diff --git a/app/app/src/main/java/cn/openp2p/OpenP2PService.kt b/app/app/src/main/java/cn/openp2p/OpenP2PService.kt index 88de756..a5e466c 100644 --- a/app/app/src/main/java/cn/openp2p/OpenP2PService.kt +++ b/app/app/src/main/java/cn/openp2p/OpenP2PService.kt @@ -4,7 +4,6 @@ import android.app.* import android.content.Context import android.content.Intent import android.graphics.Color -import java.io.IOException import android.net.VpnService import android.os.Binder import android.os.Build @@ -22,6 +21,13 @@ import java.io.FileOutputStream import java.nio.ByteBuffer import kotlinx.coroutines.* import org.json.JSONObject +import java.io.File +import java.net.InetAddress +import java.net.NetworkInterface +import kotlinx.coroutines.channels.Channel +import java.nio.channels.FileChannel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext data class Node(val name: String, val ip: String, val resource: String? = null) @@ -32,9 +38,10 @@ data class Network( val gateway: String, val Nodes: List ) + class OpenP2PService : VpnService() { companion object { - private val LOG_TAG = OpenP2PService::class.simpleName + private val LOG_TAG = "OpenP2PService" } inner class LocalBinder : Binder() { @@ -44,12 +51,17 @@ class OpenP2PService : VpnService() { private val binder = LocalBinder() private lateinit var network: openp2p.P2PNetwork private lateinit var mToken: String - private var running:Boolean =true - private var sdwanRunning:Boolean =false + private var running: Boolean = true + private var sdwanRunning: Boolean = false private var vpnInterface: ParcelFileDescriptor? = null - private var sdwanJob: Job? = null + private var sdwanJob: Job? = null + private val packetQueue = Channel(capacity = 1024) + private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + override fun onCreate() { - Log.i(LOG_TAG, "onCreate - Thread ID = " + Thread.currentThread().id) + val logDir = File(getExternalFilesDir(null), "log") + Logger.init(logDir) + Logger.i(LOG_TAG, "onCreate - Thread ID = " + Thread.currentThread().id) var channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createNotificationChannel("kim.hsl", "ForegroundService") } else { @@ -64,7 +76,7 @@ class OpenP2PService : VpnService() { val notification = channelId?.let { NotificationCompat.Builder(this, it) - // .setSmallIcon(R.mipmap.app_icon) + // .setSmallIcon(R.mipmap.app_icon) .setContentTitle("My Awesome App") .setContentText("Doing some work...") .setContentIntent(pendingIntent).build() @@ -76,7 +88,7 @@ class OpenP2PService : VpnService() { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.i( + Logger.i( LOG_TAG, "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().id ) @@ -86,20 +98,20 @@ class OpenP2PService : VpnService() { override fun onBind(p0: Intent?): IBinder? { val token = p0?.getStringExtra("token") - Log.i(LOG_TAG, "onBind token=$token") + Logger.i(LOG_TAG, "onBind token=$token") startOpenP2P(token) return binder } - private fun startOpenP2P(token : String?): Boolean { + private fun startOpenP2P(token: String?): Boolean { if (sdwanRunning) { return true } - Log.i(LOG_TAG, "startOpenP2P - Thread ID = " + Thread.currentThread().id + token) + Logger.i(LOG_TAG, "startOpenP2P - Thread ID = " + Thread.currentThread().id + token) val oldToken = Openp2p.getToken(getExternalFilesDir(null).toString()) - Log.i(LOG_TAG, "startOpenP2P oldtoken=$oldToken newtoken=$token") - if (oldToken=="0" && token==null){ + Logger.i(LOG_TAG, "startOpenP2P oldtoken=$oldToken newtoken=$token") + if (oldToken == "0" && token == null) { return false } sdwanRunning = true @@ -112,7 +124,7 @@ class OpenP2PService : VpnService() { 1 ) // /storage/emulated/0/Android/data/cn.openp2p/files/ val isConnect = network.connect(30000) // ms - Log.i(LOG_TAG, "login result: " + isConnect.toString()); + Logger.i(LOG_TAG, "login result: " + isConnect.toString()); do { Thread.sleep(1000) } while (network.connect(30000) && running) @@ -123,152 +135,257 @@ class OpenP2PService : VpnService() { private fun refreshSDWAN() { GlobalScope.launch { - Log.i(OpenP2PService.LOG_TAG, "refreshSDWAN start"); + Logger.i(OpenP2PService.LOG_TAG, "refreshSDWAN start"); while (true) { - Log.i(OpenP2PService.LOG_TAG, "waiting new sdwan config"); - val buf = ByteArray(4096) + Logger.i(OpenP2PService.LOG_TAG, "waiting new sdwan config"); + val buf = ByteArray(32 * 1024) val buffLen = Openp2p.getAndroidSDWANConfig(buf) - Log.i(OpenP2PService.LOG_TAG, "closing running sdwan instance"); + Logger.i(OpenP2PService.LOG_TAG, "closing running sdwan instance"); sdwanRunning = false vpnInterface?.close() vpnInterface = null - Thread.sleep(10000) - runSDWAN(buf.copyOfRange(0,buffLen.toInt() )) + sdwanJob?.join() + sdwanJob = serviceScope.launch(context = Dispatchers.IO) { + runSDWAN(buf.copyOfRange(0, buffLen.toInt())) + } } - Log.i(OpenP2PService.LOG_TAG, "refreshSDWAN end"); + Logger.i(OpenP2PService.LOG_TAG, "refreshSDWAN end"); } } + private suspend fun readTunLoop() { val inputStream = FileInputStream(vpnInterface?.fileDescriptor).channel - if (inputStream==null){ - Log.i(OpenP2PService.LOG_TAG, "open FileInputStream error: "); + if (inputStream == null) { + Logger.i(OpenP2PService.LOG_TAG, "open FileInputStream error: "); return } - Log.d(LOG_TAG, "read tun loop start") + Logger.i(LOG_TAG, "read tun loop start") val buffer = ByteBuffer.allocate(4096) val byteArrayRead = ByteArray(4096) while (sdwanRunning) { buffer.clear() - val readBytes = inputStream.read(buffer) - if (readBytes <= 0) { -// Log.i(OpenP2PService.LOG_TAG, "inputStream.read error: ") - delay(1) - continue + withContext(Dispatchers.IO) { + val readBytes = inputStream.read(buffer) + if (readBytes > 0) { + buffer.flip() + buffer.get(byteArrayRead, 0, readBytes) + // Logger.i(OpenP2PService.LOG_TAG, String.format("Openp2p.androidRead: %d", readBytes)) + Openp2p.androidRead(byteArrayRead, readBytes.toLong()) + // Logger.i(OpenP2PService.LOG_TAG, "inputStream.read error: ") + } else { + delay(50) + } } - buffer.flip() - buffer.get(byteArrayRead,0,readBytes) - Log.i(OpenP2PService.LOG_TAG, String.format("Openp2p.androidRead: %d", readBytes)) - Openp2p.androidRead(byteArrayRead, readBytes.toLong()) - } - Log.d(LOG_TAG, "read tun loop end") + Logger.i(LOG_TAG, "read tun loop end") } - private fun runSDWAN(buf:ByteArray) { - sdwanRunning=true - sdwanJob=GlobalScope.launch(context = Dispatchers.IO) { - Log.i(OpenP2PService.LOG_TAG, "runSDWAN start:${buf.decodeToString()}"); - try{ - var builder = Builder() - val jsonObject = JSONObject(buf.decodeToString()) - val id = jsonObject.getLong("id") - val name = jsonObject.getString("name") - val gateway = jsonObject.getString("gateway") - val nodesArray = jsonObject.getJSONArray("Nodes") - val nodesList = mutableListOf() - for (i in 0 until nodesArray.length()) { - nodesList.add(nodesArray.getJSONObject(i)) - } + private suspend fun runSDWAN(buf: ByteArray) { +// val localIps = listOf( +// "fe80::14b6:a0ff:fe3e:64de" to 64, +// "192.168.100.184" to 24, +// "10.93.158.91" to 32, +// "192.168.3.66" to 24 +// ) +// +// // 测试用例 +// val testCases = listOf( +// "192.168.3.11" to true, +// "192.168.100.1" to true, +// "192.168.101.1" to false, +// "10.93.158.91" to true, +// "10.93.158.90" to false, +// "fe80::14b6:a0ff:fe3e:64de" to true, +// "fe80::14b6:a0ff:fe3e:64dd" to true // 在同一子网 +// ) +// +// for ((ip, expected) in testCases) { +// val result = isSameSubnet(ip, localIps) +// println("Testing IP: $ip, Expected: $expected, Result: $result") +// } + sdwanRunning = true - val myNodeName = Openp2p.getAndroidNodeName() - Log.i(OpenP2PService.LOG_TAG, "getAndroidNodeName:${myNodeName}"); - val nodeList = nodesList.map { - val nodeName = it.getString("name") - val nodeIp = it.getString("ip") - if (nodeName==myNodeName){ + Logger.i(OpenP2PService.LOG_TAG, "runSDWAN start:${buf.decodeToString()}"); + try { + var builder = Builder() + val jsonObject = JSONObject(buf.decodeToString()) + // debug sdwan info + // val jsonObject = JSONObject("""{"id":2817104318517097000,"name":"network1","gateway":"10.2.3.254/24","mode":"central","centralNode":"nanjin-192-168-0-82","enable":1,"tunnelNum":3,"mtu":1420,"Nodes":[{"name":"192-168-24-15","ip":"10.2.3.5"},{"name":"Alpine Linux-172.16","ip":"10.2.3.14","resource":"172.16.0.0/24"},{"name":"ctdeMacBook-Pro.local","ip":"10.2.3.22"},{"name":"dengjiandeMBP.sh.chaitin.net","ip":"10.2.3.32"},{"name":"DESKTOP-WIN11-ARM-self","ip":"10.2.3.19"},{"name":"eastdeMBP.sh.chaitin.net","ip":"10.2.3.3"},{"name":"FN-NAS-HP","ip":"10.2.3.1","resource":"192.168.100.0/24"},{"name":"huangruideMBP.sh.chaitin.net","ip":"10.2.3.30"},{"name":"iStoreOS-virtual-machine","ip":"10.2.3.12"},{"name":"k30s-redmi-10.2.33","ip":"10.2.3.27"},{"name":"lincheng-MacBook-Pro-3.sh.chaitin.net","ip":"10.2.3.15"},{"name":"localhost-mi-13","ip":"10.2.3.8"},{"name":"localhost-华为matepad11","ip":"10.2.3.13"},{"name":"luzhanwendeMacBook-Pro.local","ip":"10.2.3.17"},{"name":"Mi-pad2-local","ip":"10.2.3.9"},{"name":"nanjin-192-168-0-82","ip":"10.2.3.34"},{"name":"R7000P-2021","ip":"10.2.3.7"},{"name":"tanxiaolongsMBP.sh.chaitin.net","ip":"10.2.3.20"},{"name":"TUF-AX3000_V2-3804","ip":"10.2.3.25"},{"name":"WIN-CYZ-10.2.3.16","ip":"10.2.3.16"},{"name":"WODOUYAO","ip":"10.2.3.4"},{"name":"Zstrack01","ip":"10.2.3.51","resource":"192.168.24.0/22,192.168.20.0/24"},{"name":"小米14-localhost","ip":"10.2.3.23"}]}""") + val id = jsonObject.getLong("id") + val mtu = jsonObject.getInt("mtu") + val name = jsonObject.getString("name") + val gateway = jsonObject.getString("gateway") + val nodesArray = jsonObject.getJSONArray("Nodes") + + val nodesList = mutableListOf() + for (i in 0 until nodesArray.length()) { + nodesList.add(nodesArray.getJSONObject(i)) + } + + val myNodeName = Openp2p.getAndroidNodeName() + // 使用本地 IP 和子网判断是否需要添加路由 + val localIps = getLocalIpAndSubnet() + Logger.i(OpenP2PService.LOG_TAG, "getAndroidNodeName:${myNodeName}"); + val nodeList = nodesList.map { + val nodeName = it.getString("name") + val nodeIp = it.getString("ip") + if (nodeName == myNodeName) { + try { + Logger.i(LOG_TAG, "Attempting to add address: $nodeIp/24") builder.addAddress(nodeIp, 24) + Logger.i(LOG_TAG, "Successfully added address") + } catch (e: Exception) { + Logger.e(LOG_TAG, "Failed to add address $nodeIp: ${e.message}") + throw e // or handle gracefully } - val nodeResource = if (it.has("resource")) it.getString("resource") else null - val parts = nodeResource?.split("/") - if (parts?.size == 2) { - val ipAddress = parts[0] - val subnetMask = parts[1] - builder.addRoute(ipAddress, subnetMask.toInt()) - Log.i(OpenP2PService.LOG_TAG, "sdwan addRoute:${ipAddress},${subnetMask.toInt()}"); + } + val nodeResource = it.optString("resource", null) + if (!nodeResource.isNullOrEmpty()) { + // 可能是多个网段,用逗号分隔 + val resourceList = nodeResource.split(",") + for (resource in resourceList) { + val parts = resource.split("/") + if (parts.size == 2) { + val ipAddress = parts[0].trim() + val subnetMask = parts[1].trim() + // 判断是否属于本机网段 + if (!isSameSubnet(ipAddress, localIps)) { + builder.addRoute(ipAddress, subnetMask.toInt()) + Logger.i( + OpenP2PService.LOG_TAG, + "sdwan addRoute:${ipAddress},${subnetMask}" + ) + } else { + Logger.i( + OpenP2PService.LOG_TAG, + "Skipped adding route for ${ipAddress}, already in local subnet" + ) + } + } else { + Logger.w(OpenP2PService.LOG_TAG, "Invalid resource format: $resource") + } } - Node(nodeName, nodeIp, nodeResource) } - val network = Network(id, name, gateway, nodeList) - println(network) - Log.i(OpenP2PService.LOG_TAG, "onBind"); - builder.addDnsServer("223.5.5.5") - builder.addDnsServer("2400:3200::1") // alicloud dns v6 & v4 - builder.addRoute("10.2.3.0", 24) -// builder.addRoute("0.0.0.0", 0); - builder.setSession(LOG_TAG!!) - builder.setMtu(1420) - vpnInterface = builder.establish() - if (vpnInterface==null){ - Log.e(OpenP2PService.LOG_TAG, "start vpnservice error: "); - } - val outputStream = FileOutputStream(vpnInterface?.fileDescriptor).channel - if (outputStream==null){ - Log.e(OpenP2PService.LOG_TAG, "open FileOutputStream error: "); - return@launch - } - - val byteArrayWrite = ByteArray(4096) - launch { - readTunLoop() - } - - Log.d(LOG_TAG, "write tun loop start") - while (sdwanRunning) { - val len = Openp2p.androidWrite(byteArrayWrite) - Log.i(OpenP2PService.LOG_TAG, String.format("Openp2p.androidWrite: %d",len)); - val writeBytes = outputStream?.write(ByteBuffer.wrap(byteArrayWrite)) - if (writeBytes != null && writeBytes <= 0) { - Log.i(OpenP2PService.LOG_TAG, "outputStream?.write error: "); - continue - } - } - outputStream.close() -// 关闭 VPN 接口 - vpnInterface?.close() -// 置空变量以释放资源 - vpnInterface = null - Log.d(LOG_TAG, "write tun loop end") - }catch (e: Exception) { - // 捕获异常并记录 - Log.e("VPN Connection", "发生异常: ${e.message}") + Node(nodeName, nodeIp, nodeResource) } - Log.i(OpenP2PService.LOG_TAG, "runSDWAN end"); + + val network = Network(id, name, gateway, nodeList) + println(network) + Logger.i(OpenP2PService.LOG_TAG, "onBind"); + builder.addDnsServer("119.29.29.29") + builder.addDnsServer("2400:3200::1") // alicloud dns v6 & v4 + // builder.addRoute("10.2.3.0", 24) +// builder.addRoute("0.0.0.0", 0); + val gatewayStr = jsonObject.optString("gateway", "") + val subNet = getNetworkAddress(gatewayStr) + if (subNet != null) { + val (netIp, prefix) = subNet + builder.addRoute(netIp, prefix) + Logger.i(OpenP2PService.LOG_TAG, "Added route from gateway: $netIp/$prefix") + } else { + Logger.w(OpenP2PService.LOG_TAG, "Invalid gateway format: $gatewayStr") + } + + builder.setSession(LOG_TAG!!) + builder.setMtu(mtu) + vpnInterface = builder.establish() + if (vpnInterface == null) { + Log.e(OpenP2PService.LOG_TAG, "start vpnservice error: "); + } + + val byteArrayWrite = ByteArray(4096) + serviceScope.launch(Dispatchers.IO) { + readTunLoop() // 文件读操作,适合 Dispatchers.IO + } + + val outputStream = FileOutputStream(vpnInterface?.fileDescriptor).channel + if (outputStream == null) { + Log.e(OpenP2PService.LOG_TAG, "open FileOutputStream error: "); + return + } + Logger.i(LOG_TAG, "write tun loop start") + while (sdwanRunning) { + val len = Openp2p.androidWrite(byteArrayWrite, 3000) + if (len > mtu || len.toInt() == 0) { + continue + } + try { + val writeBytes = + outputStream?.write(ByteBuffer.wrap(byteArrayWrite, 0, len.toInt())) + if (writeBytes != null && writeBytes <= 0) { + Logger.e(LOG_TAG, "outputStream.write failed: $writeBytes") + } + } catch (e: Exception) { + Logger.e(LOG_TAG, "outputStream.write exception: ${e.message}") + e.printStackTrace() + continue + } + } + outputStream.close() + vpnInterface?.close() + vpnInterface = null + Logger.i(LOG_TAG, "write tun loop end") + } catch (e: Exception) { + Logger.i("VPN Connection", "发生异常: ${e.message}") } - + Logger.i(OpenP2PService.LOG_TAG, "runSDWAN end"); } + /** + * 将 "10.2.3.254/16" 这样的 CIDR 转成正确对齐的网络地址,如 "10.2.0.0/16" + */ + fun getNetworkAddress(cidr: String): Pair? { + val parts = cidr.trim().split("/") + if (parts.size != 2) return null + val ip = parts[0] + val prefix = parts[1].toIntOrNull() ?: return null + if (prefix !in 0..32) return null + + val octets = ip.split(".").map { it.toInt() } + if (octets.size != 4) return null + + // 转成整数 + val ipInt = (octets[0] shl 24) or (octets[1] shl 16) or (octets[2] shl 8) or octets[3] + + // 生成掩码并计算网络地址 + val mask = if (prefix == 0) 0 else (-1 shl (32 - prefix)) + val networkInt = ipInt and mask + + // 转回点分十进制 + val networkIp = listOf( + (networkInt shr 24) and 0xFF, + (networkInt shr 16) and 0xFF, + (networkInt shr 8) and 0xFF, + networkInt and 0xFF + ).joinToString(".") + + return networkIp to prefix + } override fun onDestroy() { - Log.i(LOG_TAG, "onDestroy - Thread ID = " + Thread.currentThread().id) super.onDestroy() + Logger.i(LOG_TAG, "onDestroy - Canceling service scope") + serviceScope.cancel() // 取消所有与服务相关的协程 } override fun onUnbind(intent: Intent?): Boolean { - Log.i(LOG_TAG, "onUnbind - Thread ID = " + Thread.currentThread().id) + Logger.i(LOG_TAG, "onUnbind - Thread ID = " + Thread.currentThread().id) stopSelf() return super.onUnbind(intent) } + fun isConnected(): Boolean { if (!::network.isInitialized) return false return network.connect(1000) } fun stop() { - running=false + running = false stopSelf() Openp2p.stop() } + @RequiresApi(Build.VERSION_CODES.O) private fun createNotificationChannel(channelId: String, channelName: String): String? { val chan = NotificationChannel( @@ -281,4 +398,55 @@ class OpenP2PService : VpnService() { service.createNotificationChannel(chan) return channelId } +} + +// 获取本机所有IP地址和对应的子网信息 +fun getLocalIpAndSubnet(): List> { + val localIps = mutableListOf>() + val networkInterfaces = NetworkInterface.getNetworkInterfaces() + // 手动添加测试数据 + //localIps.add(Pair("192.168.3.33", 24)) + while (networkInterfaces.hasMoreElements()) { + val networkInterface = networkInterfaces.nextElement() + if (networkInterface.isUp && !networkInterface.isLoopback) { + val interfaceAddresses = networkInterface.interfaceAddresses + for (interfaceAddress in interfaceAddresses) { + val address = interfaceAddress.address + val prefixLength = interfaceAddress.networkPrefixLength + if (address is InetAddress) { + localIps.add(Pair(address.hostAddress, prefixLength.toInt())) + } + } + } + } + return localIps +} + +// 判断某个IP是否与本机某网段匹配 +fun isSameSubnet(ipAddress: String, localIps: List>): Boolean { + val targetIp = InetAddress.getByName(ipAddress).address + for ((localIp, prefixLength) in localIps) { + val localIpBytes = InetAddress.getByName(localIp).address + val mask = createSubnetMask(prefixLength, localIpBytes.size) // 动态生成掩码 + + // 比较目标 IP 和本地 IP 的网络部分 + if (targetIp.indices.all { i -> + (targetIp[i].toInt() and mask[i].toInt()) == (localIpBytes[i].toInt() and mask[i].toInt()) + }) { + return true + } + } + return false +} + +// 根据前缀长度动态生成子网掩码 +fun createSubnetMask(prefixLength: Int, addressLength: Int): ByteArray { + val mask = ByteArray(addressLength) + for (i in 0 until prefixLength / 8) { + mask[i] = 0xFF.toByte() + } + if (prefixLength % 8 != 0) { + mask[prefixLength / 8] = (0xFF shl (8 - (prefixLength % 8))).toByte() + } + return mask } \ No newline at end of file diff --git a/app/app/src/main/java/cn/openp2p/log.kt b/app/app/src/main/java/cn/openp2p/log.kt index 28a26fc..ad3e6b9 100644 --- a/app/app/src/main/java/cn/openp2p/log.kt +++ b/app/app/src/main/java/cn/openp2p/log.kt @@ -1,45 +1,91 @@ package cn.openp2p -import android.content.* +import android.util.Log +import java.text.SimpleDateFormat +import java.io.BufferedWriter import java.io.File import java.io.FileWriter -import java.text.SimpleDateFormat -import java.util.* -import android.app.* -import android.content.Context -import android.content.Intent -import android.graphics.Color import java.io.IOException -import android.net.VpnService -import android.os.Binder -import android.os.Build -import android.os.IBinder -import android.os.ParcelFileDescriptor -import android.util.Log -import androidx.annotation.RequiresApi -import androidx.core.app.NotificationCompat -import cn.openp2p.ui.login.LoginActivity -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import openp2p.Openp2p -import java.io.FileInputStream -import java.io.FileOutputStream -import java.nio.ByteBuffer -import kotlinx.coroutines.* -import org.json.JSONObject +import java.util.Date +import java.util.Locale object Logger { - private val logFile: File = File("app.log") + private const val LOG_TAG = "OpenP2PLogger" + private var logFile: File? = null + private var bufferedWriter: BufferedWriter? = null - fun log(message: String) { - val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date()) - val logMessage = "$timestamp: $message\n" + // 初始化日志文件 + fun init(logDir: File, logFileName: String = "app.log") { + if (!logDir.exists()) logDir.mkdirs() + logFile = File(logDir, logFileName) try { - val fileWriter = FileWriter(logFile, true) - fileWriter.append(logMessage) - fileWriter.close() - } catch (e: Exception) { - e.printStackTrace() + bufferedWriter = BufferedWriter(FileWriter(logFile, true)) + } catch (e: IOException) { + Log.e(LOG_TAG, "Failed to initialize BufferedWriter: ${e.message}") } } + + // 写日志(线程安全) + @Synchronized + fun log(level: String, tag: String, message: String, throwable: Throwable? = null) { + val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date()) + val logMessage = "$timestamp $level $tag: $message" + + // 打印到 console + when (level) { + "ERROR" -> Log.e(tag, message, throwable) + "WARN" -> Log.w(tag, message, throwable) + "INFO" -> Log.i(tag, message) + "DEBUG" -> Log.d(tag, message) + "VERBOSE" -> Log.v(tag, message) + } + + // 写入文件 + try { + bufferedWriter?.apply { + write(logMessage) + newLine() + flush() + } + throwable?.let { + bufferedWriter?.apply { + write(Log.getStackTraceString(it)) + newLine() + flush() + } + } + } catch (e: IOException) { + Log.e(LOG_TAG, "Failed to write log to file: ${e.message}") + } + } + + // 清理资源 + fun close() { + try { + bufferedWriter?.close() + } catch (e: IOException) { + Log.e(LOG_TAG, "Failed to close BufferedWriter: ${e.message}") + } + } + + // 简化方法 + fun e(tag: String, message: String, throwable: Throwable? = null) { + log("ERROR", tag, message, throwable) + } + + fun w(tag: String, message: String, throwable: Throwable? = null) { + log("WARN", tag, message, throwable) + } + + fun i(tag: String, message: String) { + log("INFO", tag, message) + } + + fun d(tag: String, message: String) { + log("DEBUG", tag, message) + } + + fun v(tag: String, message: String) { + log("VERBOSE", tag, message) + } } diff --git a/cmd/openp2p.go b/cmd/openp2p.go index e9eed29..ab07b10 100644 --- a/cmd/openp2p.go +++ b/cmd/openp2p.go @@ -1,9 +1,9 @@ package main import ( - op "openp2p/core" + op2p "openp2p/core" ) func main() { - op.Run() + op2p.Run() } diff --git a/core/errorcode.go b/core/errorcode.go index cadc547..21d0749 100644 --- a/core/errorcode.go +++ b/core/errorcode.go @@ -30,4 +30,7 @@ var ( ErrBuildTunnelBusy = errors.New("build tunnel busy") ErrMemAppTunnelNotFound = errors.New("memapp tunnel not found") ErrRemoteServiceUnable = errors.New("remote service unable") + ErrAppWithoutTunnel = errors.New("p2papp has no available tunnel") + ErrWriteWindowFull = errors.New("writeWindow full") + ErrHeaderDataLen = errors.New("header datalen error") ) diff --git a/core/install.go b/core/install.go index b2af6b9..45f8d93 100644 --- a/core/install.go +++ b/core/install.go @@ -11,26 +11,14 @@ import ( ) func install() { - gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion) - gLog.Println(LvINFO, "Contact: QQ group 16947733, Email openp2p.cn@gmail.com") - gLog.Println(LvINFO, "install start") - defer gLog.Println(LvINFO, "install end") - // auto uninstall - err := os.MkdirAll(defaultInstallPath, 0775) - - if err != nil { - gLog.Printf(LvERROR, "MkdirAll %s error:%s", defaultInstallPath, err) - return - } - err = os.Chdir(defaultInstallPath) - if err != nil { - gLog.Println(LvERROR, "Chdir error:", err) - return - } - - uninstall() - // save config file + gLog.i("openp2p start. version: %s", OpenP2PVersion) + gLog.i("Contact: QQ group 16947733, Email openp2p.cn@gmail.com") + gLog.i("install start") + defer gLog.i("install end") parseParams("install", "") + // auto uninstall + uninstall(false) + gLog.i("install path: %s", defaultInstallPath) targetPath := filepath.Join(defaultInstallPath, defaultBinName) d := daemon{} // copy files @@ -38,37 +26,42 @@ func install() { binPath, _ := os.Executable() src, errFiles := os.Open(binPath) // can not use args[0], on Windows call openp2p is ok(=openp2p.exe) if errFiles != nil { - gLog.Printf(LvERROR, "os.Open %s error:%s", os.Args[0], errFiles) + gLog.e("os.Open %s error:%s", os.Args[0], errFiles) return } dst, errFiles := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775) if errFiles != nil { - gLog.Printf(LvERROR, "os.OpenFile %s error:%s", targetPath, errFiles) - return + time.Sleep(time.Second * 5) // maybe windows defender occupied the file, retry + dst, errFiles = os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775) + if errFiles != nil { + gLog.e("os.OpenFile %s error:%s", targetPath, errFiles) + return + } } _, errFiles = io.Copy(dst, src) if errFiles != nil { - gLog.Printf(LvERROR, "io.Copy error:%s", errFiles) + gLog.e("io.Copy error:%s", errFiles) return } src.Close() dst.Close() // install system service - err = d.Control("install", targetPath, []string{"-d"}) + err := d.Control("install", targetPath, []string{"-d"}) if err == nil { - gLog.Println(LvINFO, "install system service ok.") + gLog.i("install system service ok.") } time.Sleep(time.Second * 2) err = d.Control("start", targetPath, []string{"-d"}) if err != nil { - gLog.Println(LvERROR, "start openp2p service error:", err) + gLog.e("start openp2p service error:%s", err) } else { - gLog.Println(LvINFO, "start openp2p service ok.") + gLog.i("start openp2p service ok.") } - gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn") + gConf.save() + gLog.i("Visit WebUI on https://console.openp2p.cn") } func installByFilename() { @@ -78,7 +71,7 @@ func installByFilename() { } serverHost := params[1] token := params[2] - gLog.Println(LvINFO, "install start") + gLog.i("install start") targetPath := os.Args[0] args := []string{"install"} args = append(args, "-serverhost") @@ -93,31 +86,38 @@ func installByFilename() { cmd.Env = env err := cmd.Run() if err != nil { - gLog.Println(LvERROR, "install by filename, start process error:", err) + gLog.e("install by filename, start process error:%s", err) return } - gLog.Println(LvINFO, "install end") - gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn") + gLog.i("install end") + gLog.i("Visit WebUI on https://console.openp2p.cn") fmt.Println("Press the Any Key to exit") fmt.Scanln() os.Exit(0) } -func uninstall() { - gLog.Println(LvINFO, "uninstall start") - defer gLog.Println(LvINFO, "uninstall end") + +func uninstall(rmFiles bool) { + gLog.i("uninstall start") + defer gLog.i("uninstall end") d := daemon{} err := d.Control("stop", "", nil) if err != nil { // service maybe not install - return + gLog.d("stop service error:%s", err) } err = d.Control("uninstall", "", nil) if err != nil { - gLog.Println(LvERROR, "uninstall system service error:", err) + gLog.d("uninstall system service error:%s", err) } else { - gLog.Println(LvINFO, "uninstall system service ok.") + gLog.i("uninstall system service ok.") } + time.Sleep(time.Second * 3) binPath := filepath.Join(defaultInstallPath, defaultBinName) os.Remove(binPath + "0") os.Remove(binPath) - // os.RemoveAll(defaultInstallPath) // reserve config.json + if rmFiles { + if err := os.RemoveAll(defaultInstallPath); err != nil { + gLog.e("RemoveAll %s error:%s", defaultInstallPath, err) + } + } + } diff --git a/core/log.go b/core/log.go index 19d3c37..c38193c 100644 --- a/core/log.go +++ b/core/log.go @@ -3,38 +3,33 @@ package openp2p import ( "log" "os" + "path/filepath" "runtime" "sync" + "sync/atomic" "time" ) -type LogLevel int +type LogLevel int32 var gLog *logger const ( LvDev LogLevel = -1 - LvDEBUG LogLevel = iota - LvINFO - LvWARN - LvERROR + LvDEBUG LogLevel = 0 + LvINFO LogLevel = 1 + LvWARN LogLevel = 2 + LvERROR LogLevel = 3 ) -var ( - logFileNames map[LogLevel]string - loglevel map[LogLevel]string -) - -func init() { - logFileNames = make(map[LogLevel]string) - loglevel = make(map[LogLevel]string) - logFileNames[0] = ".log" - loglevel[LvDEBUG] = "DEBUG" - loglevel[LvINFO] = "INFO" - loglevel[LvWARN] = "WARN" - loglevel[LvERROR] = "ERROR" - loglevel[LvDev] = "Dev" +const logFileNames string = ".log" +var loglevel = map[LogLevel]string{ + LvDEBUG: "DEBUG", + LvINFO: "INFO", + LvWARN: "WARN", + LvERROR: "ERROR", + LvDev: "Dev", } const ( @@ -43,62 +38,54 @@ const ( ) type logger struct { - loggers map[LogLevel]*log.Logger - files map[LogLevel]*os.File - level LogLevel - logDir string - mtx *sync.Mutex - lineEnding string - pid int - maxLogSize int64 - mode int - stdLogger *log.Logger + logger *log.Logger + files *os.File + level atomic.Int32 + logDir string + mtx sync.Mutex + lineEnding string + pid int + maxLogSize atomic.Int64 + mode int + stdLogger *log.Logger + checkFileRunning bool } func NewLogger(path string, filePrefix string, level LogLevel, maxLogSize int64, mode int) *logger { - loggers := make(map[LogLevel]*log.Logger) - logfiles := make(map[LogLevel]*os.File) - var ( - logdir string - ) - if path == "" { - logdir = "log/" - } else { - logdir = path + "/log/" + logdir := filepath.Join(path, "log") + if err := os.MkdirAll(logdir, 0755); err != nil && mode&LogFile != 0 { + return nil } - os.MkdirAll(logdir, 0777) - for lv := range logFileNames { - logFilePath := logdir + filePrefix + logFileNames[lv] - f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - if err != nil { - log.Fatal(err) - } - os.Chmod(logFilePath, 0644) - logfiles[lv] = f - loggers[lv] = log.New(f, "", log.LstdFlags|log.Lmicroseconds) + logFilePath := filepath.Join(logdir, filePrefix+logFileNames) + f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil && mode&LogFile != 0 { + log.Fatal(err) } - var le string + stdLog := log.New(f, "", log.LstdFlags|log.Lmicroseconds) + le := "\n" if runtime.GOOS == "windows" { le = "\r\n" - } else { - le = "\n" } - pLog := &logger{loggers, logfiles, level, logdir, &sync.Mutex{}, le, os.Getpid(), maxLogSize, mode, log.New(os.Stdout, "", 0)} + pLog := &logger{logger: stdLog, + files: f, + logDir: logdir, + lineEnding: le, + pid: os.Getpid(), + mode: mode, + stdLogger: log.New(os.Stdout, "", 0)} + pLog.setMaxSize(maxLogSize) + pLog.setLevel(level) pLog.stdLogger.SetFlags(log.LstdFlags | log.Lmicroseconds) go pLog.checkFile() return pLog } func (l *logger) setLevel(level LogLevel) { - l.mtx.Lock() - defer l.mtx.Unlock() - l.level = level + l.level.Store(int32(level)) } func (l *logger) setMaxSize(size int64) { - l.mtx.Lock() - defer l.mtx.Unlock() - l.maxLogSize = size + l.maxLogSize.Store(size) } func (l *logger) setMode(mode int) { @@ -107,49 +94,61 @@ func (l *logger) setMode(mode int) { l.mode = mode } +func (l *logger) close() { + l.checkFileRunning = false + l.files.Close() +} + func (l *logger) checkFile() { - if l.maxLogSize <= 0 { + if l.maxLogSize.Load() <= 0 { return } + l.checkFileRunning = true ticker := time.NewTicker(time.Minute) - for { + for l.checkFileRunning { select { case <-ticker.C: - l.mtx.Lock() - for lv, logFile := range l.files { - f, e := logFile.Stat() - if e != nil { - continue - } - if f.Size() <= l.maxLogSize { - continue - } - logFile.Close() - fname := f.Name() - backupPath := l.logDir + fname + ".0" - os.Remove(backupPath) - os.Rename(l.logDir+fname, backupPath) - newFile, e := os.OpenFile(l.logDir+fname, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - if e == nil { - l.loggers[lv].SetOutput(newFile) - l.files[lv] = newFile - } + f, e := l.files.Stat() + if e != nil { + continue } - l.mtx.Unlock() + if f.Size() <= l.maxLogSize.Load() { + continue + } + l.mtx.Lock() + l.files.Close() + fname := f.Name() + backupPath := filepath.Join(l.logDir, fname+".0") + err := os.Remove(backupPath) + if err != nil { + log.Println("remove openp2p.log0 error:", err) + } + if err = os.Rename(filepath.Join(l.logDir, fname), backupPath); err != nil { + log.Println("rename openp2p.log error:", err) + } + if newFile, e := os.OpenFile(filepath.Join(l.logDir, fname), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); e == nil { + + l.logger.SetOutput(newFile) + l.files = newFile + l.mtx.Unlock() + } + case <-time.After(time.Second * 1): } + } } func (l *logger) Printf(level LogLevel, format string, params ...interface{}) { - l.mtx.Lock() - defer l.mtx.Unlock() - if level < l.level { + if level < LogLevel(l.level.Load()) { return } + l.mtx.Lock() + defer l.mtx.Unlock() + pidAndLevel := []interface{}{l.pid, loglevel[level]} params = append(pidAndLevel, params...) if l.mode&LogFile != 0 { - l.loggers[0].Printf("%d %s "+format+l.lineEnding, params...) + l.logger.Printf("%d %s "+format+l.lineEnding, params...) } if l.mode&LogConsole != 0 { l.stdLogger.Printf("%d %s "+format+l.lineEnding, params...) @@ -157,18 +156,38 @@ func (l *logger) Printf(level LogLevel, format string, params ...interface{}) { } func (l *logger) Println(level LogLevel, params ...interface{}) { - l.mtx.Lock() - defer l.mtx.Unlock() - if level < l.level { + if level < LogLevel(l.level.Load()) { return } + l.mtx.Lock() + defer l.mtx.Unlock() pidAndLevel := []interface{}{l.pid, " ", loglevel[level], " "} params = append(pidAndLevel, params...) params = append(params, l.lineEnding) if l.mode&LogFile != 0 { - l.loggers[0].Print(params...) + l.logger.Print(params...) } if l.mode&LogConsole != 0 { l.stdLogger.Print(params...) } } + +func (l *logger) d(format string, params ...interface{}) { + l.Printf(LvDEBUG, format, params...) +} + +func (l *logger) i(format string, params ...interface{}) { + l.Printf(LvINFO, format, params...) +} + +func (l *logger) w(format string, params ...interface{}) { + l.Printf(LvWARN, format, params...) +} + +func (l *logger) e(format string, params ...interface{}) { + l.Printf(LvERROR, format, params...) +} + +func (l *logger) dev(format string, params ...interface{}) { + l.Printf(LvDev, format, params...) +} diff --git a/core/openp2p.go b/core/openp2p.go index 2f2f938..235f47b 100644 --- a/core/openp2p.go +++ b/core/openp2p.go @@ -25,7 +25,7 @@ func Run() { install() return case "uninstall": - uninstall() + uninstall(true) return } } else { diff --git a/core/optun.go b/core/optun.go index 231ff9a..e2f6958 100644 --- a/core/optun.go +++ b/core/optun.go @@ -4,7 +4,10 @@ import ( "github.com/openp2p-cn/wireguard-go/tun" ) +const optunMTU = 1420 + var AndroidSDWANConfig chan []byte +var preAndroidSDWANConfig string type optun struct { tunName string diff --git a/core/optun_android.go b/core/optun_android.go index 6f90666..e45d73d 100644 --- a/core/optun_android.go +++ b/core/optun_android.go @@ -5,12 +5,14 @@ package openp2p import ( - "net" + "time" ) const ( - tunIfaceName = "optun" - PIHeaderSize = 0 + tunIfaceName = "optun" + PIHeaderSize = 0 + ReadTunBuffSize = 2048 + ReadTunBuffNum = 16 ) var AndroidReadTun chan []byte // TODO: multi channel @@ -35,27 +37,35 @@ func (t *optun) Write(bufs [][]byte, offset int) (int, error) { func AndroidRead(data []byte, len int) { head := PacketHeader{} parseHeader(data, &head) - gLog.Printf(LvDev, "AndroidRead tun dst ip=%s,len=%d", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len) + // gLog.dev("AndroidRead tun dst ip=%s,len=%d", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len) buf := make([]byte, len) copy(buf, data) AndroidReadTun <- buf } -func AndroidWrite(buf []byte) int { - p := <-AndroidWriteTun - copy(buf, p) - return len(p) +func AndroidWrite(buf []byte, timeoutMs int) int { + timeout := time.Duration(timeoutMs) * time.Millisecond + select { + case p := <-AndroidWriteTun: + if len(p) > int(gConf.sdwan.Mtu) { + gLog.e("AndroidWrite packet too large %d", len(p)) + } + copy(buf, p) + return len(p) + case <-time.After(timeout): + return 0 + } } func GetAndroidSDWANConfig(buf []byte) int { p := <-AndroidSDWANConfig copy(buf, p) - gLog.Printf(LvINFO, "AndroidSDWANConfig=%s", p) + gLog.i("AndroidSDWANConfig=%s", p) return len(p) } func GetAndroidNodeName() string { - gLog.Printf(LvINFO, "GetAndroidNodeName=%s", gConf.Network.Node) + gLog.i("GetAndroidNodeName=%s", gConf.Network.Node) return gConf.Network.Node } diff --git a/core/udp.go b/core/udp.go index 9f11eed..6109e22 100644 --- a/core/udp.go +++ b/core/udp.go @@ -22,7 +22,7 @@ func UDPRead(conn *net.UDPConn, timeout time.Duration) (ra net.Addr, head *openP if timeout > 0 { err = conn.SetReadDeadline(time.Now().Add(timeout)) if err != nil { - gLog.Println(LvERROR, "SetReadDeadline error") + gLog.e("SetReadDeadline error") return nil, nil, nil, 0, err } } @@ -35,9 +35,13 @@ func UDPRead(conn *net.UDPConn, timeout time.Duration) (ra net.Addr, head *openP } head = &openP2PHeader{} err = binary.Read(bytes.NewReader(buff[:openP2PHeaderSize]), binary.LittleEndian, head) - if err != nil || head.DataLen > uint32(len(buff)-openP2PHeaderSize) { - gLog.Println(LvERROR, "parse p2pheader error:", err) + if err != nil { + gLog.e("parse p2pheader error:%s", err) return nil, nil, nil, 0, err } + if head.DataLen > uint32(len(buff)-openP2PHeaderSize) { + gLog.e("parse p2pheader error:%d", ErrHeaderDataLen) + return nil, nil, nil, 0, ErrHeaderDataLen + } return } diff --git a/core/upnp.go b/core/upnp.go index c4672cc..ee6b0ea 100644 --- a/core/upnp.go +++ b/core/upnp.go @@ -63,13 +63,15 @@ func Discover() (nat NAT, err error) { return } var n int + socket.SetDeadline(time.Now().Add(3 * time.Second)) _, _, err = socket.ReadFromUDP(answerBytes) if err != nil { - gLog.Println(LvDEBUG, "UPNP discover error:", err) + gLog.d("UPNP discover error:%s", err) return } for { + socket.SetDeadline(time.Now().Add(3 * time.Second)) n, _, err = socket.ReadFromUDP(answerBytes) if err != nil { break @@ -266,7 +268,11 @@ func soapRequest(url, function, message, domain string) (r *http.Response, err e // log.Stderr("soapRequest ", req) - r, err = http.DefaultClient.Do(req) + client := &http.Client{ + Timeout: 3 * time.Second, + } + + r, err = client.Do(req) if err != nil { return nil, err } diff --git a/core/util_darwin.go b/core/util_darwin.go index 6a10f37..f8f4909 100644 --- a/core/util_darwin.go +++ b/core/util_darwin.go @@ -5,9 +5,12 @@ import ( "syscall" ) -const ( +var ( defaultInstallPath = "/usr/local/openp2p" - defaultBinName = "openp2p" +) + +const ( + defaultBinName = "openp2p" ) func getOsName() (osName string) { diff --git a/core/util_freebsd.go b/core/util_freebsd.go index b9d4ff3..4ea690f 100644 --- a/core/util_freebsd.go +++ b/core/util_freebsd.go @@ -9,9 +9,12 @@ import ( "syscall" ) -const ( +var ( defaultInstallPath = "/usr/local/openp2p" - defaultBinName = "openp2p" +) + +const ( + defaultBinName = "openp2p" ) func getOsName() (osName string) { diff --git a/core/util_linux.go b/core/util_linux.go index a918596..1d95f17 100644 --- a/core/util_linux.go +++ b/core/util_linux.go @@ -10,9 +10,12 @@ import ( "syscall" ) -const ( +var ( defaultInstallPath = "/usr/local/openp2p" - defaultBinName = "openp2p" +) + +const ( + defaultBinName = "openp2p" ) func getOsName() (osName string) { diff --git a/core/util_windows.go b/core/util_windows.go index 7e6fcd3..3136d3a 100644 --- a/core/util_windows.go +++ b/core/util_windows.go @@ -11,9 +11,12 @@ import ( "golang.org/x/sys/windows/registry" ) -const ( +var ( defaultInstallPath = "C:\\Program Files\\OpenP2P" - defaultBinName = "openp2p.exe" +) + +const ( + defaultBinName = "openp2p.exe" ) func getOsName() (osName string) { @@ -47,7 +50,7 @@ func setRLimit() error { func setFirewall() { fullPath, err := filepath.Abs(os.Args[0]) if err != nil { - gLog.Println(LvERROR, "add firewall error:", err) + gLog.e("add firewall error:%s", err) return } isXP := false diff --git a/go.mod b/go.mod index 00d9a3d..7cbaf12 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,15 @@ go 1.20 require ( github.com/emirpasic/gods v1.18.1 - github.com/gorilla/websocket v1.4.2 + github.com/gorilla/websocket v1.5.3 github.com/openp2p-cn/go-reuseport v0.3.2 github.com/openp2p-cn/service v1.0.0 github.com/openp2p-cn/totp v0.0.0-20230421034602-0f3320ffb25e + github.com/openp2p-cn/wireguard-go v0.0.20241020 github.com/quic-go/quic-go v0.34.0 github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 github.com/xtaci/kcp-go/v5 v5.5.17 + golang.org/x/net v0.30.0 golang.org/x/sys v0.26.0 golang.zx2c4.com/wireguard/windows v0.5.3 ) @@ -33,9 +35,9 @@ require ( golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/tools v0.26.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect gvisor.dev/gvisor v0.0.0-20241128011400-745828301c93 // indirect )