diff --git a/android/app/src/main/kotlin/com/network/proxy/plugin/AppInfo.kt b/android/app/src/main/kotlin/com/network/proxy/plugin/AppInfo.kt index 10dfabc..ed9172c 100644 --- a/android/app/src/main/kotlin/com/network/proxy/plugin/AppInfo.kt +++ b/android/app/src/main/kotlin/com/network/proxy/plugin/AppInfo.kt @@ -8,7 +8,7 @@ import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import java.io.ByteArrayOutputStream -class AppInfo(name: CharSequence, packageName: String, icon: ByteArray?, versionName: String?) : +class ProcessInfo(name: CharSequence, packageName: String, icon: ByteArray?, versionName: String?) : HashMap() { init { put("name", name) @@ -22,7 +22,7 @@ class AppInfo(name: CharSequence, packageName: String, icon: ByteArray?, version packageManager: PackageManager, app: ApplicationInfo, withIcon: Boolean = true - ): AppInfo { + ): ProcessInfo { val name = packageManager.getApplicationLabel(app) val packageName = app.packageName val icon = @@ -31,7 +31,7 @@ class AppInfo(name: CharSequence, packageName: String, icon: ByteArray?, version // 部分应用可能没有设置versionName,将导致获取列表操作失败 val versionName = packageInfo.versionName ?: "" - return AppInfo(name, packageName, icon, versionName) + return ProcessInfo(name, packageName, icon, versionName) } private fun drawableToByteArray(drawable: Drawable): ByteArray { diff --git a/android/app/src/main/kotlin/com/network/proxy/plugin/InstalledAppsPlugin.kt b/android/app/src/main/kotlin/com/network/proxy/plugin/InstalledAppsPlugin.kt index 9ade047..2b3373f 100644 --- a/android/app/src/main/kotlin/com/network/proxy/plugin/InstalledAppsPlugin.kt +++ b/android/app/src/main/kotlin/com/network/proxy/plugin/InstalledAppsPlugin.kt @@ -41,17 +41,17 @@ class InstalledAppsPlugin : AndroidFlutterPlugin() { } } - private fun getAppInfo(packageName: String): AppInfo { + private fun getAppInfo(packageName: String): ProcessInfo { val packageManager = activity.packageManager packageManager.getApplicationInfo(packageName, 0).let { app -> - return AppInfo.create(packageManager, app, true) + return ProcessInfo.create(packageManager, app, true) } } private fun getInstalledApps( withIcon: Boolean, packageNamePrefix: String - ): List { + ): List { val packageManager = activity.packageManager var installedApps = packageManager.getInstalledApplications(0) installedApps = @@ -70,8 +70,8 @@ class InstalledAppsPlugin : AndroidFlutterPlugin() { val threadPoolExecutor = Executors.newFixedThreadPool(6) installedApps.map { app -> - val task: Callable = Callable { - AppInfo.create(packageManager, app, withIcon) + val task: Callable = Callable { + ProcessInfo.create(packageManager, app, withIcon) } threadPoolExecutor.submit(task) }.map { future -> diff --git a/android/app/src/main/kotlin/com/network/proxy/vpn/ConnectionHandler.kt b/android/app/src/main/kotlin/com/network/proxy/vpn/ConnectionHandler.kt index 5dce873..c095528 100644 --- a/android/app/src/main/kotlin/com/network/proxy/vpn/ConnectionHandler.kt +++ b/android/app/src/main/kotlin/com/network/proxy/vpn/ConnectionHandler.kt @@ -16,7 +16,6 @@ import com.network.proxy.vpn.util.PacketUtil.getOutput import com.network.proxy.vpn.util.PacketUtil.intToIPAddress import com.network.proxy.vpn.util.PacketUtil.isPacketCorrupted import com.network.proxy.vpn.util.ProcessInfoManager -import com.network.proxy.vpn.util.TLS.getDomain import com.network.proxy.vpn.util.TLS.isTLSClientHello import java.io.IOException import java.net.InetAddress @@ -179,29 +178,8 @@ class ConnectionHandler( //any data from client? if (dataLength > 0) { - if (!connection.isInitConnect) { - connection.isInitConnect = true - val proxyAddress = - getProxyAddress(clientPacketData, destinationIP, destinationPort) - try { - val channel = connection.channel as SocketChannel? - val connected = channel!!.connect(proxyAddress) - connection.isConnected = connected - nioService.registerSession(connection) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && proxyAddress == manager.proxyAddress) { - //获取进程信息 - ProcessInfoManager.instance.setConnectionOwnerUid(connection) - Log.d( - TAG, - "Proxy Initiate connecting key:" + key + " " + channel.localAddress + " to remote tcp server: " + channel.remoteAddress - ) - } - } catch (e: Exception) { - val ips = intToIPAddress(destinationIP) - Log.w(TAG, "Failed to reconnect to $ips:$destinationPort", e) - } - } + //init proxy + initProxyConnect(clientPacketData, destinationIP, destinationPort, connection) //accumulate data from client if (connection.recSequence == 0L || tcpHeader.sequenceNumber >= connection.recSequence) { @@ -269,6 +247,37 @@ class ConnectionHandler( } } + private fun initProxyConnect( + clientPacketData: ByteBuffer, destinationIP: Int, destinationPort: Int, + connection: Connection + ) { + if (connection.isInitConnect) { + return + } + + connection.isInitConnect = true + val proxyAddress = + getProxyAddress(clientPacketData, destinationIP, destinationPort) + try { + val channel = connection.channel as SocketChannel? + val connected = channel!!.connect(proxyAddress) + connection.isConnected = connected + nioService.registerSession(connection) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && proxyAddress == manager.proxyAddress) { + //获取进程信息 + ProcessInfoManager.instance.setConnectionOwnerUid(connection) + Log.d( + TAG, + "Proxy Initiate connecting key:" + connection.toString() + " " + channel.localAddress + " to remote tcp server: " + channel.remoteAddress + ) + } + } catch (e: Exception) { + val ips = intToIPAddress(destinationIP) + Log.w(TAG, "Failed to reconnect to $ips:$destinationPort", e) + } + } + private fun sendRstPacket(ip: IP4Header, tcp: TCPHeader, dataLength: Int) { val data = TCPPacketFactory.createRstData(ip, tcp, dataLength) writer.write(data) diff --git a/android/app/src/main/kotlin/com/network/proxy/vpn/util/ProcessInfoManager.kt b/android/app/src/main/kotlin/com/network/proxy/vpn/util/ProcessInfoManager.kt index d2bd9f6..8438842 100644 --- a/android/app/src/main/kotlin/com/network/proxy/vpn/util/ProcessInfoManager.kt +++ b/android/app/src/main/kotlin/com/network/proxy/vpn/util/ProcessInfoManager.kt @@ -6,7 +6,7 @@ import android.os.Build import android.os.Process import android.system.OsConstants import com.google.common.cache.CacheBuilder -import com.network.proxy.plugin.AppInfo +import com.network.proxy.plugin.ProcessInfo import com.network.proxy.vpn.Connection import java.net.InetSocketAddress import java.nio.channels.SocketChannel @@ -22,13 +22,15 @@ class ProcessInfoManager private constructor() { val instance = ProcessInfoManager() } - private val localPortUidMap = + class NetworkInfo(val uid: Int, val remoteHost: String, val remotePort: Int) + + private val localPortMap = CacheBuilder.newBuilder().maximumSize(10_000).expireAfterAccess(60, TimeUnit.SECONDS) - .build() + .build() private val appInfoCache = CacheBuilder.newBuilder().maximumSize(10_000).expireAfterAccess(300, TimeUnit.SECONDS) - .build() + .build() var activity: Context? = null @@ -47,10 +49,25 @@ class ProcessInfoManager private constructor() { val channel = connection.channel if (uid != null && channel is SocketChannel) { val localAddress = channel.localAddress as InetSocketAddress - localPortUidMap.put(localAddress.port, uid) + val networkInfo = + NetworkInfo(uid, destinationAddress.hostString, destinationAddress.port) + localPortMap.put(localAddress.port, networkInfo) } } + fun removeConnection(connection: Connection) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return + } + + val channel = connection.channel + if (channel is SocketChannel) { + val localAddress = channel.localAddress as InetSocketAddress + localPortMap.invalidate(localAddress.port) + } + } + + private fun getProcessInfo( localAddress: InetSocketAddress, remoteAddress: InetSocketAddress ): Int? { @@ -84,15 +101,20 @@ class ProcessInfoManager private constructor() { return null } - fun getProcessInfoByPort(localPort: Int): AppInfo? { - val uid = localPortUidMap.getIfPresent(localPort) - if (uid != null) { - return getProcessInfo(uid) + fun getProcessInfoByPort(localPort: Int): ProcessInfo? { + val networkInfo = localPortMap.getIfPresent(localPort) + if (networkInfo != null) { + val processInfo = getProcessInfo(networkInfo.uid) + + return processInfo?.apply { + put("remoteHost", networkInfo.remoteHost) + put("remotePort", networkInfo.remotePort) + } } return null } - private fun getProcessInfo(uid: Int): AppInfo? { + private fun getProcessInfo(uid: Int): ProcessInfo? { var appInfo = appInfoCache.getIfPresent(uid) if (appInfo != null) return appInfo @@ -100,7 +122,7 @@ class ProcessInfoManager private constructor() { val pkgNames = packageManager?.getPackagesForUid(uid) ?: return null for (pkgName in pkgNames) { val applicationInfo = packageManager.getApplicationInfo(pkgName, 0) - appInfo = AppInfo.create(packageManager, applicationInfo) + appInfo = ProcessInfo.create(packageManager, applicationInfo) appInfoCache.put(uid, appInfo) return appInfo } diff --git a/lib/native/process_info.dart b/lib/native/process_info.dart index ce9534c..b80bc09 100644 --- a/lib/native/process_info.dart +++ b/lib/native/process_info.dart @@ -1,11 +1,15 @@ import 'package:flutter/services.dart'; -import 'package:network_proxy/native/installed_apps.dart'; +import 'package:network_proxy/network/util/process_info.dart'; class ProcessInfoPlugin { static const MethodChannel _methodChannel = MethodChannel('com.proxy/processInfo'); - static Future getProcessByPort(String host, int port) { - return _methodChannel.invokeMethod('getProcessByPort', {"host": host, "port": port}).then( - (value) => value == null ? null : AppInfo.formJson(value)); + static Future getProcessByPort(String host, int port) { + return _methodChannel.invokeMethod('getProcessByPort', {"host": host, "port": port}).then((process) { + if (process == null) return null; + + return ProcessInfo(process['packageName'], process['name'], process['packageName'], + icon: process['icon'], remoteHost: process['remoteHost'], remotePost: process['remotePost']); + }); } } diff --git a/lib/network/network.dart b/lib/network/network.dart index a8b42d3..b677a68 100644 --- a/lib/network/network.dart +++ b/lib/network/network.dart @@ -132,17 +132,25 @@ class Server extends Network { void ssl(ChannelContext channelContext, Channel channel, Uint8List data) async { var hostAndPort = channelContext.host; try { - if (hostAndPort == null && TLS.getDomain(data) != null) { - hostAndPort = HostAndPort.host(TLS.getDomain(data)!, 443); + if (hostAndPort == null) { + var domain = TLS.getDomain(data); + var port = 443; + if (domain == null) { + var process = await ProcessInfoUtils.getProcessByPort( + channel.remoteSocketAddress, channel.remoteSocketAddress.toString()); + domain = process?.remoteHost; + port = process?.remotePost ?? port; + } + hostAndPort = HostAndPort.host(domain!, port); } - hostAndPort?.scheme = HostAndPort.httpsScheme; - channelContext.putAttribute(AttributeKeys.domain, hostAndPort?.host); + hostAndPort.scheme = HostAndPort.httpsScheme; + channelContext.putAttribute(AttributeKeys.domain, hostAndPort.host); Channel? remoteChannel = channelContext.serverChannel; - if (HostFilter.filter(hostAndPort?.host) || !configuration.enableSsl) { - remoteChannel = remoteChannel ?? await channelContext.connectServerChannel(hostAndPort!, RelayHandler(channel)); + if (HostFilter.filter(hostAndPort.host) || !configuration.enableSsl) { + remoteChannel = remoteChannel ?? await channelContext.connectServerChannel(hostAndPort, RelayHandler(channel)); relay(channel, remoteChannel); channel.pipeline.channelRead(channelContext, channel, data); return; @@ -151,12 +159,12 @@ class Server extends Network { if (remoteChannel != null && !remoteChannel.isSsl) { var supportProtocols = configuration.enabledHttp2 ? TLS.supportProtocols(data) : null; var secureSocket = await SecureSocket.secure(remoteChannel.socket, - supportedProtocols: supportProtocols, host: hostAndPort?.host, onBadCertificate: (certificate) => true); + supportedProtocols: supportProtocols, host: hostAndPort.host, onBadCertificate: (certificate) => true); remoteChannel.secureSocket(secureSocket, channelContext); } //ssl自签证书 - var certificate = await CertificateManager.getCertificateContext(hostAndPort!.host); + var certificate = await CertificateManager.getCertificateContext(hostAndPort.host); var selectedProtocol = remoteChannel?.selectedProtocol; if (selectedProtocol != null) certificate.setAlpnProtocols([selectedProtocol], true); diff --git a/lib/network/util/process_info.dart b/lib/network/util/process_info.dart index b027fa3..6ce7243 100644 --- a/lib/network/util/process_info.dart +++ b/lib/network/util/process_info.dart @@ -23,7 +23,7 @@ class ProcessInfoUtils { if (Platform.isAndroid) { var app = await ProcessInfoPlugin.getProcessByPort(socketAddress.host, socketAddress.port); if (app != null) { - return ProcessInfo(app.packageName ?? '', app.name ?? '', app.name ?? '', icon: app.icon); + return app; } if (socketAddress.host == '127.0.0.1') return ProcessInfo('com.network.proxy', "ProxyPin", ''); return null; @@ -116,8 +116,10 @@ class ProcessInfo { final String path; Uint8List? icon; + String? remoteHost; + int? remotePost; - ProcessInfo(this.id, this.name, this.path, {this.icon}); + ProcessInfo(this.id, this.name, this.path, {this.icon, this.remoteHost, this.remotePost}); factory ProcessInfo.fromJson(Map json) { return ProcessInfo(json['id'], json['name'], json['path']);