diff --git a/lib/network/bin/configuration.dart b/lib/network/bin/configuration.dart index 5785087..1962895 100644 --- a/lib/network/bin/configuration.dart +++ b/lib/network/bin/configuration.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io'; +import 'package:network_proxy/network/host_port.dart'; import 'package:network_proxy/network/util/host_filter.dart'; import 'package:network_proxy/network/util/logger.dart'; import 'package:network_proxy/network/util/request_rewrite.dart'; @@ -13,8 +14,8 @@ class Configuration { //是否启用https抓包 bool enableSsl = false; - //是否启用桌面抓包 - bool enableDesktop = true; + //是否设置系统代理 + bool enableSystemProxy = true; //是否引导 bool guide = false; @@ -94,7 +95,7 @@ class Configuration { logger.i('加载配置文件 [$file]'); port = config['port'] ?? port; enableSsl = config['enableSsl'] == true; - enableDesktop = config['enableDesktop'] ?? true; + enableSystemProxy = config['enableSystemProxy'] ?? (config['enableDesktop'] ?? true); guide = config['guide'] ?? false; upgradeNotice = config['upgradeNotice'] ?? true; if (config['externalProxy'] != null) { @@ -140,33 +141,10 @@ class Configuration { 'upgradeNotice': upgradeNotice, 'port': port, 'enableSsl': enableSsl, - 'enableDesktop': enableDesktop, + 'enableSystemProxy': enableSystemProxy, 'externalProxy': externalProxy?.toJson(), 'whitelist': HostFilter.whitelist.toJson(), 'blacklist': HostFilter.blacklist.toJson(), }; } } - -/// 代理信息 -class ProxyInfo { - bool enable = false; - String host = '127.0.0.1'; - int? port; - - ProxyInfo(); - - ProxyInfo.fromJson(Map json) { - enable = json['enable'] == true; - host = json['host']; - port = json['port']; - } - - Map toJson() { - return { - 'enable': enable, - 'host': host, - 'port': port, - }; - } -} diff --git a/lib/network/bin/server.dart b/lib/network/bin/server.dart index b885f7c..473a4e6 100644 --- a/lib/network/bin/server.dart +++ b/lib/network/bin/server.dart @@ -3,9 +3,9 @@ import 'dart:io'; import 'package:network_proxy/network/bin/configuration.dart'; -import '../channel.dart'; import '../handler.dart'; import '../http/codec.dart'; +import '../network.dart'; import '../util/logger.dart'; import '../util/system_proxy.dart'; @@ -58,8 +58,8 @@ class ProxyServer { return server.bind(port).then((serverSocket) { logger.i("listen on $port"); this.server = server; - if (configuration.enableDesktop) { - SystemProxy.setSystemProxy(port, enableSsl); + if (configuration.enableSystemProxy) { + setSystemProxyEnable(true); } return server; }); @@ -68,17 +68,24 @@ class ProxyServer { /// 停止代理服务 Future stop() async { logger.i("stop on $port"); - if (configuration.enableDesktop) { - if (Platform.isMacOS) { - await SystemProxy.setProxyEnableMacOS(false, enableSsl); - } else if (Platform.isWindows) { - await SystemProxy.setProxyEnableWindows(false); - } + if (configuration.enableSystemProxy) { + await setSystemProxyEnable(false); } await server?.stop(); return server; } + /// 设置系统代理 + setSystemProxyEnable(bool enable) async { + //关闭系统代理 恢复成外部代理地址 + if (!enable && configuration.externalProxy?.enabled == true) { + await SystemProxy.setSystemProxy(configuration.externalProxy!.port!, enableSsl); + return; + } + + await SystemProxy.setSystemProxyEnable(port, enable, enableSsl); + } + /// 重启代理服务 restart() { stop().then((value) => start()); diff --git a/lib/network/channel.dart b/lib/network/channel.dart index 65d8716..28227cf 100644 --- a/lib/network/channel.dart +++ b/lib/network/channel.dart @@ -1,14 +1,12 @@ +import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; -import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/host_port.dart'; import 'package:network_proxy/network/http/codec.dart'; import 'package:network_proxy/network/http/http.dart'; import 'package:network_proxy/network/util/attribute_keys.dart'; -import 'package:network_proxy/network/util/crts.dart'; -import 'package:network_proxy/network/util/host_filter.dart'; import 'package:network_proxy/network/util/logger.dart'; import 'handler.dart'; @@ -59,13 +57,23 @@ class Channel { Socket get socket => _socket; - set secureSocket(SecureSocket secureSocket) => _socket = secureSocket; + set secureSocket(SecureSocket secureSocket) { + _socket = secureSocket; + pipeline.listen(this); + } Future write(Object obj) async { if (isClosed) { logger.w("channel is closed $obj"); return; } + + //只能有一个写入 + int retry = 0; + while (isWriting && retry++ < 30) { + await Future.delayed(const Duration(milliseconds: 100)); + } + isWriting = true; try { var data = pipeline._encoder.encode(obj); @@ -93,8 +101,8 @@ class Channel { while (isWriting && retry++ < 10) { await Future.delayed(const Duration(milliseconds: 150)); } - _socket.destroy(); isOpen = false; + _socket.destroy(); } ///返回此channel是否打开 @@ -120,12 +128,12 @@ class Channel { class ChannelPipeline extends ChannelHandler { late Decoder _decoder; late Encoder _encoder; - late ChannelHandler _handler; + late ChannelHandler handler; handle(Decoder decoder, Encoder encoder, ChannelHandler handler) { _encoder = encoder; _decoder = decoder; - _handler = handler; + this.handler = handler; } void listen(Channel channel) { @@ -136,7 +144,7 @@ class ChannelPipeline extends ChannelHandler { @override void channelActive(Channel channel) { - _handler.channelActive(channel); + handler.channelActive(channel); } /// 转发请求 @@ -152,7 +160,7 @@ class ChannelPipeline extends ChannelHandler { HostAndPort? remote = channel.getAttribute(AttributeKeys.remote); if (remote != null && channel.getAttribute(channel.id) != null) { relay(channel, channel.getAttribute(channel.id)); - _handler.channelRead(channel, msg); + handler.channelRead(channel, msg); return; } @@ -178,20 +186,20 @@ class ChannelPipeline extends ChannelHandler { if (data is HttpResponse) { data.remoteAddress = '${channel.remoteAddress.host}:${channel.remotePort}'; } - _handler.channelRead(channel, data!); + handler.channelRead(channel, data!); } catch (error, trace) { exceptionCaught(channel, error, trace: trace); } } @override - exceptionCaught(Channel channel, dynamic cause, {StackTrace? trace}) { - _handler.exceptionCaught(channel, cause, trace: trace); + exceptionCaught(Channel channel, dynamic error, {StackTrace? trace}) { + handler.exceptionCaught(channel, error, trace: trace); } @override channelInactive(Channel channel) { - _handler.channelInactive(channel); + handler.channelInactive(channel); } } @@ -210,122 +218,3 @@ class RawCodec extends Codec { abstract interface class ChannelInitializer { void initChannel(Channel channel); } - -class Network { - late Function _channelInitializer; - String? remoteHost; - Configuration? configuration; - - Network initChannel(void Function(Channel channel) initializer) { - _channelInitializer = initializer; - return this; - } - - Channel listen(Socket socket) { - var channel = Channel(socket); - _channelInitializer.call(channel); - channel.pipeline.channelActive(channel); - socket.listen((data) => _onEvent(data, channel), - onError: (error, StackTrace trace) => channel.pipeline.exceptionCaught(channel, error, trace: trace), - onDone: () => channel.pipeline.channelInactive(channel)); - return channel; - } - - _onEvent(Uint8List data, Channel channel) async { - if (remoteHost != null) { - channel.putAttribute(AttributeKeys.remote, HostAndPort.of(remoteHost!)); - } - - //代理信息 - if (configuration?.externalProxy?.enable == true) { - channel.putAttribute(AttributeKeys.proxyInfo, configuration!.externalProxy!); - } - - HostAndPort? hostAndPort = channel.getAttribute(AttributeKeys.host); - - //黑名单 或 没开启https 直接转发 - if (HostFilter.filter(hostAndPort?.host) || (hostAndPort?.isSsl() == true && configuration?.enableSsl == false)) { - relay(channel, channel.getAttribute(channel.id)); - channel.pipeline.channelRead(channel, data); - return; - } - - //ssl握手 - if (hostAndPort?.isSsl() == true) { - ssl(channel, hostAndPort!, data); - return; - } - - channel.pipeline.channelRead(channel, data); - } - - void ssl(Channel channel, HostAndPort hostAndPort, Uint8List data) async { - try { - //客户端ssl握手 - Channel remoteChannel = channel.getAttribute(channel.id); - remoteChannel.secureSocket = - await SecureSocket.secure(remoteChannel.socket, onBadCertificate: (certificate) => true); - - remoteChannel.pipeline.listen(remoteChannel); - - //ssl自签证书 - var certificate = await CertificateManager.getCertificateContext(hostAndPort.host); - - SecureSocket secureSocket = await SecureSocket.secureServer(channel.socket, certificate, bufferedData: data); - channel.secureSocket = secureSocket; - channel.pipeline.listen(channel); - } catch (error, trace) { - channel.pipeline._handler.exceptionCaught(channel, error, trace: trace); - } - } - - /// 转发请求 - void relay(Channel clientChannel, Channel remoteChannel) { - var rawCodec = RawCodec(); - clientChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(remoteChannel)); - remoteChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(clientChannel)); - } -} - -class Server extends Network { - late ServerSocket serverSocket; - bool isRunning = false; - - Server(Configuration configuration) { - super.configuration = configuration; - } - - Future bind(int port) async { - serverSocket = await ServerSocket.bind(InternetAddress.anyIPv4, port); - serverSocket.listen((socket) { - listen(socket); - }); - isRunning = true; - return serverSocket; - } - - Future stop() async { - if (!isRunning) return serverSocket; - isRunning = false; - await serverSocket.close(); - return serverSocket; - } -} - -class Client extends Network { - Future connect(HostAndPort hostAndPort) async { - String host = hostAndPort.host; - //说明支持ipv6 - if (host.startsWith("[") && host.endsWith(']')) { - host = host.substring(host.lastIndexOf(":") + 1, host.length - 1); - } - - return Socket.connect(host, hostAndPort.port, timeout: const Duration(seconds: 3)).then((socket) => listen(socket)); - } - - /// ssl连接 - Future secureConnect(HostAndPort hostAndPort) async { - return SecureSocket.connect(hostAndPort.host, hostAndPort.port, - timeout: const Duration(seconds: 3), onBadCertificate: (certificate) => true).then((socket) => listen(socket)); - } -} diff --git a/lib/network/handler.dart b/lib/network/handler.dart index 619312e..1f5054d 100644 --- a/lib/network/handler.dart +++ b/lib/network/handler.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/host_port.dart'; import 'package:network_proxy/network/http/http.dart'; import 'package:network_proxy/network/http/http_headers.dart'; @@ -86,11 +85,13 @@ class HttpChannelHandler extends ChannelHandler { /// 转发请求 Future forward(Channel channel, HttpRequest httpRequest) async { + //获取远程连接 var remoteChannel = await _getRemoteChannel(channel, httpRequest); + //实现抓包代理转发 if (httpRequest.method != HttpMethod.connect) { - // log.i("[${channel.id}] ${httpRequest.requestUrl}"); + // log.i("[${channel.id}] ${httpRequest.method.name} ${httpRequest.requestUrl}"); var replaceBody = requestRewrites?.findRequestReplaceWith(httpRequest.path()); if (replaceBody?.isNotEmpty == true) { @@ -100,7 +101,6 @@ class HttpChannelHandler extends ChannelHandler { if (!HostFilter.filter(httpRequest.hostAndPort?.host)) { listener?.onRequest(channel, httpRequest); } - //实现抓包代理转发 await remoteChannel.write(httpRequest); } } @@ -139,27 +139,23 @@ class HttpChannelHandler extends ChannelHandler { //远程转发 HostAndPort? remote = clientChannel.getAttribute(AttributeKeys.remote); - if (remote != null) { - var proxyChannel = await HttpClients.rawConnect(remote, proxyHandler); + //外部代理 + ProxyInfo? proxyInfo = clientChannel.getAttribute(AttributeKeys.proxyInfo); + + if (remote != null || proxyInfo != null) { + HostAndPort connectHost = remote ?? HostAndPort.host(proxyInfo!.host, proxyInfo.port!); + var proxyChannel = await HttpClients.startConnect(connectHost, proxyHandler); clientChannel.putAttribute(clientId, proxyChannel); proxyChannel.write(httpRequest); return proxyChannel; } - //https代理 - ProxyInfo? proxyInfo = clientChannel.getAttribute(AttributeKeys.proxyInfo); - if (proxyInfo != null) { - var proxyChannel = await HttpClients.rawConnect(HostAndPort.host(proxyInfo.host, proxyInfo.port!), proxyHandler); - clientChannel.putAttribute(clientId, proxyChannel); - await proxyChannel.write(httpRequest); - return proxyChannel; - } - - var proxyChannel = await HttpClients.rawConnect(hostAndPort, proxyHandler); + var proxyChannel = await HttpClients.startConnect(hostAndPort, proxyHandler); clientChannel.putAttribute(clientId, proxyChannel); //https代理新建连接请求 if (httpRequest.method == HttpMethod.connect) { - await clientChannel.write(HttpResponse(httpRequest.protocolVersion, HttpStatus.ok)); + await clientChannel + .write(HttpResponse(httpRequest.protocolVersion, HttpStatus.ok.reason('Connection established'))); } return proxyChannel; @@ -168,6 +164,7 @@ class HttpChannelHandler extends ChannelHandler { /// http响应代理 class HttpResponseProxyHandler extends ChannelHandler { + //客户端的连接 final Channel clientChannel; EventListener? listener; @@ -176,10 +173,10 @@ class HttpResponseProxyHandler extends ChannelHandler { HttpResponseProxyHandler(this.clientChannel, {this.listener, this.requestRewrites}); @override - void channelRead(Channel channel, HttpResponse msg) { + void channelRead(Channel channel, HttpResponse msg) async { msg.request = clientChannel.getAttribute(AttributeKeys.request); msg.request?.response = msg; - // log.i("[${clientChannel.id}] Response ${msg.bodyAsString}"); + // log.i("[${clientChannel.id}] Response ${msg}"); var replaceBody = requestRewrites?.findResponseReplaceWith(msg.request?.path()); if (replaceBody?.isNotEmpty == true) { @@ -189,8 +186,9 @@ class HttpResponseProxyHandler extends ChannelHandler { if (!HostFilter.filter(msg.request?.hostAndPort?.host)) { listener?.onResponse(clientChannel, msg); } + //发送给客户端 - clientChannel.write(msg); + await clientChannel.write(msg); } @override diff --git a/lib/network/host_port.dart b/lib/network/host_port.dart index 19c30be..f72274d 100644 --- a/lib/network/host_port.dart +++ b/lib/network/host_port.dart @@ -21,8 +21,8 @@ class HostAndPort { HostAndPort(this.scheme, this.host, this.port); - factory HostAndPort.host(String host, int port) { - return HostAndPort(port == 443 ? httpsScheme : httpScheme, host, port); + factory HostAndPort.host(String host, int port, {String? scheme}) { + return HostAndPort(scheme ?? (port == 443 ? httpsScheme : httpScheme), host, port); } bool isSsl() { @@ -75,3 +75,33 @@ class HostAndPort { return domain; } } + +/// 代理信息 +class ProxyInfo { + bool enabled = false; + String host = '127.0.0.1'; + int? port; + + ProxyInfo(); + + ProxyInfo.of(this.host, this.port) : enabled = true; + + ProxyInfo.fromJson(Map json) { + enabled = json['enabled'] == true; + host = json['host']; + port = json['port']; + } + + Map toJson() { + return { + 'enabled': enabled, + 'host': host, + 'port': port, + }; + } + + @override + String toString() { + return '{enabled: $enabled, host: $host, port: $port}'; + } +} diff --git a/lib/network/http/codec.dart b/lib/network/http/codec.dart index 5177389..90aa2f5 100644 --- a/lib/network/http/codec.dart +++ b/lib/network/http/codec.dart @@ -43,7 +43,7 @@ abstract interface class Encoder { /// 编解码器 abstract class Codec implements Decoder, Encoder { static const int defaultMaxInitialLineLength = 10240; - static const int maxBodyLength = 1024000; + static const int maxBodyLength = 4096000; } /// http编解码 diff --git a/lib/network/http/http.dart b/lib/network/http/http.dart index fc22628..5d429e8 100644 --- a/lib/network/http/http.dart +++ b/lib/network/http/http.dart @@ -261,7 +261,12 @@ class HttpStatus { } final int code; - final String reasonPhrase; + String reasonPhrase; + + HttpStatus reason(String reasonPhrase) { + this.reasonPhrase = reasonPhrase; + return this; + } HttpStatus(this.code, this.reasonPhrase); diff --git a/lib/network/http_client.dart b/lib/network/http_client.dart index 0901517..59dbcfe 100644 --- a/lib/network/http_client.dart +++ b/lib/network/http_client.dart @@ -3,19 +3,53 @@ import 'dart:io'; import 'package:network_proxy/network/host_port.dart'; import 'package:network_proxy/network/http/http.dart'; +import 'package:network_proxy/network/network.dart'; import 'channel.dart'; import 'http/codec.dart'; class HttpClients { /// 建立连接 - static Future rawConnect(HostAndPort hostAndPort, ChannelHandler handler) async { + static Future startConnect(HostAndPort hostAndPort, ChannelHandler handler) async { var client = Client() ..initChannel((channel) => channel.pipeline.handle(HttpResponseCodec(), HttpRequestCodec(), handler)); return client.connect(hostAndPort); } + ///代理建立连接 + static Future proxyConnect(HostAndPort hostAndPort, ChannelHandler handler, {ProxyInfo? proxyInfo}) async { + var client = Client() + ..initChannel((channel) => channel.pipeline.handle(HttpResponseCodec(), HttpRequestCodec(), handler)); + + HostAndPort connectHost = proxyInfo == null ? hostAndPort : HostAndPort.host(proxyInfo.host, proxyInfo.port!); + var channel = await client.connect(connectHost); + + if (proxyInfo == null || !hostAndPort.isSsl()) { + return channel; + } + + //代理 发送connect请求 + var httpResponseHandler = HttpResponseHandler(); + channel.pipeline.handler = httpResponseHandler; + + HttpRequest proxyRequest = HttpRequest(HttpMethod.connect, '${hostAndPort.host}:${hostAndPort.port}'); + proxyRequest.headers.set(HttpHeaders.hostHeader, '${hostAndPort.host}:${hostAndPort.port}'); + + await channel.write(proxyRequest); + var response = await httpResponseHandler.getResponse(const Duration(seconds: 3)); + + channel.pipeline.handler = handler; + + if (!response.status.isSuccessful()) { + final error = "$hostAndPort Proxy failed to establish tunnel " + "(${response.status.code} ${response..status.reasonPhrase})"; + throw Exception(error); + } + + return channel; + } + /// 建立连接 static Future connect(Uri uri, ChannelHandler handler) async { Client client = Client() @@ -52,20 +86,14 @@ class HttpClients { {Duration timeout = const Duration(seconds: 3)}) async { var httpResponseHandler = HttpResponseHandler(); - bool isHttps = request.uri.startsWith("https://"); - var client = Client() - ..initChannel((channel) => channel.pipeline.handle(HttpResponseCodec(), HttpRequestCodec(), httpResponseHandler)); + HostAndPort hostPort = HostAndPort.of(request.uri); - Channel channel = await client.connect(HostAndPort.host(proxyHost, port)); + Channel channel = await proxyConnect(proxyInfo: ProxyInfo.of(proxyHost, port), hostPort, httpResponseHandler); - if (isHttps) { - HttpRequest proxyRequest = HttpRequest(HttpMethod.connect, request.uri); - await channel.write(proxyRequest); - await httpResponseHandler.getResponse(timeout); + if (hostPort.isSsl()) { channel.secureSocket = await SecureSocket.secure(channel.socket, onBadCertificate: (certificate) => true); } - httpResponseHandler.resetResponse(); await channel.write(request); return httpResponseHandler.getResponse(timeout).whenComplete(() => channel.close()); } @@ -76,7 +104,7 @@ class HttpResponseHandler extends ChannelHandler { @override void channelRead(Channel channel, HttpResponse msg) { - // log.i("[${channel.id}] Response ${msg.bodyAsString}"); + // log.i("[${channel.id}] Response $msg"); _completer.complete(msg); } diff --git a/lib/network/network.dart b/lib/network/network.dart new file mode 100644 index 0000000..47e166c --- /dev/null +++ b/lib/network/network.dart @@ -0,0 +1,138 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:network_proxy/network/bin/configuration.dart'; +import 'package:network_proxy/network/channel.dart'; +import 'package:network_proxy/network/handler.dart'; +import 'package:network_proxy/network/util/attribute_keys.dart'; +import 'package:network_proxy/network/util/crts.dart'; +import 'package:network_proxy/network/util/host_filter.dart'; + +import 'host_port.dart'; + +class Network { + late Function _channelInitializer; + String? remoteHost; + Configuration? configuration; + StreamSubscription? subscription; + + Network initChannel(void Function(Channel channel) initializer) { + _channelInitializer = initializer; + return this; + } + + Channel listen(Socket socket) { + var channel = Channel(socket); + _channelInitializer.call(channel); + channel.pipeline.channelActive(channel); + subscription = socket.listen((data) => _onEvent(data, channel), + onError: (error, StackTrace trace) => channel.pipeline.exceptionCaught(channel, error, trace: trace), + onDone: () => channel.pipeline.channelInactive(channel)); + return channel; + } + + _onEvent(Uint8List data, Channel channel) async { + if (remoteHost != null) { + channel.putAttribute(AttributeKeys.remote, HostAndPort.of(remoteHost!)); + } + + //代理信息 + if (configuration?.externalProxy?.enabled == true) { + channel.putAttribute(AttributeKeys.proxyInfo, configuration!.externalProxy!); + } + + HostAndPort? hostAndPort = channel.getAttribute(AttributeKeys.host); + + //黑名单 或 没开启https 直接转发 + if (HostFilter.filter(hostAndPort?.host) || (hostAndPort?.isSsl() == true && configuration?.enableSsl == false)) { + relay(channel, channel.getAttribute(channel.id)); + channel.pipeline.channelRead(channel, data); + return; + } + + //ssl握手 + if (hostAndPort?.isSsl() == true) { + ssl(channel, hostAndPort!, data); + return; + } + + channel.pipeline.channelRead(channel, data); + } + + void ssl(Channel channel, HostAndPort hostAndPort, Uint8List data) async { + try { + Channel remoteChannel = channel.getAttribute(channel.id); + + remoteChannel.secureSocket = await SecureSocket.secure(remoteChannel.socket, + host: hostAndPort.host, onBadCertificate: (certificate) => true); + + //ssl自签证书 + var certificate = await CertificateManager.getCertificateContext(hostAndPort.host); + //服务端等待客户端ssl握手 + channel.secureSocket = await SecureSocket.secureServer(channel.socket, certificate, bufferedData: data); + } catch (error, trace) { + if (error is HandshakeException) { + channel.socket.destroy(); + subscription?.pause(); + await subscription?.cancel(); + } + channel.pipeline.exceptionCaught(channel, error, trace: trace); + } + } + + /// 转发请求 + void relay(Channel clientChannel, Channel remoteChannel) { + var rawCodec = RawCodec(); + clientChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(remoteChannel)); + remoteChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(clientChannel)); + } +} + +class Server extends Network { + late ServerSocket serverSocket; + bool isRunning = false; + + Server(Configuration configuration) { + super.configuration = configuration; + } + + Future bind(int port) async { + serverSocket = await ServerSocket.bind(InternetAddress.anyIPv4, port); + serverSocket.listen((socket) { + listen(socket); + }); + isRunning = true; + return serverSocket; + } + + Future stop() async { + if (!isRunning) return serverSocket; + isRunning = false; + await serverSocket.close(); + return serverSocket; + } +} + +class Client extends Network { + Future connect(HostAndPort hostAndPort) async { + String host = hostAndPort.host; + //说明支持ipv6 + if (host.startsWith("[") && host.endsWith(']')) { + host = host.substring(host.lastIndexOf(":") + 1, host.length - 1); + } + + return Socket.connect(host, hostAndPort.port).then((socket) { + if (socket.address.type != InternetAddressType.unix) { + socket.setOption(SocketOption.tcpNoDelay, true); + } + return listen(socket); + }); + } + + /// ssl连接 + Future secureConnect(HostAndPort hostAndPort) async { + return SecureSocket.connect(hostAndPort.host, hostAndPort.port, + timeout: const Duration(seconds: 3), onBadCertificate: (certificate) => true).then((socket) => listen(socket)); + } +} diff --git a/lib/network/util/logger.dart b/lib/network/util/logger.dart index 6b9b5df..40e68d0 100644 --- a/lib/network/util/logger.dart +++ b/lib/network/util/logger.dart @@ -3,7 +3,7 @@ import 'package:logger/logger.dart'; final logger = Logger( printer: PrettyPrinter( methodCount: 0, - errorMethodCount: 8, + errorMethodCount: 15, lineLength: 120, colors: true, printEmojis: false, diff --git a/lib/network/util/system_proxy.dart b/lib/network/util/system_proxy.dart index 43fe081..1bf417a 100644 --- a/lib/network/util/system_proxy.dart +++ b/lib/network/util/system_proxy.dart @@ -7,23 +7,26 @@ class SystemProxy { static String? _hardwarePort; /// 设置系统代理 - static void setSystemProxy(int port, bool sslSetting) async { + static Future setSystemProxy(int port, bool sslSetting) async { if (Platform.isMacOS) { - _setProxyServerMacOS(port, sslSetting); + await _setProxyServerMacOS(port, sslSetting); } else if (Platform.isWindows) { - _setProxyServerWindows(port, sslSetting); + await _setProxyServerWindows(port); } } - static void setSystemProxyEnable(int port, bool enable, bool sslSetting) async { + /// 设置系统代理 @param sslSetting 是否设置https代理只在mac中有效 + static Future setSystemProxyEnable(int port, bool enable, bool sslSetting) async { + //启用系统代理 if (enable) { - setSystemProxy(port, sslSetting); + await setSystemProxy(port, sslSetting); return; } + if (Platform.isMacOS) { - setProxyEnableMacOS(enable, sslSetting); + await setProxyEnableMacOS(enable, sslSetting); } else if (Platform.isWindows) { - setProxyEnableWindows(enable); + await setProxyEnableWindows(enable); } } @@ -34,7 +37,7 @@ class SystemProxy { _concatCommands([ 'networksetup -setwebproxy $_hardwarePort 127.0.0.1 $port', sslSetting == true ? 'networksetup -setsecurewebproxy $_hardwarePort 127.0.0.1 $port' : '', - 'networksetup -setproxybypassdomains $_hardwarePort 192.168.0.0/16 10.0.0.0/8 172.16.0.0/12 127.0.0.1 localhost *.local timestamp.apple.com sequoia.apple.com seed-sequoia.siri.apple.com *.google.com', + 'networksetup -setproxybypassdomains $_hardwarePort 192.168.0.0/16 10.0.0.0/8 172.16.0.0/12 127.0.0.1 localhost *.local timestamp.apple.com', ]) ]); print('set proxyServer, name: $_hardwarePort, exitCode: ${results.exitCode}, stdout: ${results.stdout}'); @@ -79,10 +82,12 @@ class SystemProxy { return results.stdout.toString().split(", ")[0]; } - static Future _setProxyServerWindows(int proxyPort, bool sslSetting) async { - ProxyManager manager = ProxyManager(); - await manager.setAsSystemProxy(ProxyTypes.http, "127.0.0.1", proxyPort); + static Future _setProxyServerWindows(int proxyPort) async { + print("setSystemProxy $proxyPort"); + ProxyManager manager = ProxyManager(); + await manager.setAsSystemProxy(ProxyTypes.https, "127.0.0.1", proxyPort); + print("setSystemProxy end"); var results = await Process.run('reg', [ 'add', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings', diff --git a/lib/network/util/x509.dart b/lib/network/util/x509.dart index 28ae47f..75b5086 100644 --- a/lib/network/util/x509.dart +++ b/lib/network/util/x509.dart @@ -161,18 +161,6 @@ class X509Generate { return outer; } - /// - /// Converts the hex string to bytes - /// - static Uint8List _stringAsBytes(String s) { - var list = StringUtils.chunk(s, 2); - var bytes = []; - for (var e in list) { - bytes.add(int.parse(e, radix: 16)); - } - return Uint8List.fromList(bytes); - } - static String _getDigestFromOi(String oi) { switch (oi) { case 'ecdsaWithSHA1': diff --git a/lib/ui/desktop/left/domain.dart b/lib/ui/desktop/left/domain.dart index c125deb..bc19d37 100644 --- a/lib/ui/desktop/left/domain.dart +++ b/lib/ui/desktop/left/domain.dart @@ -38,7 +38,7 @@ class DomainWidgetState extends State { changeState() { if (!changing) { changing = true; - Future.delayed(const Duration(milliseconds: 1500), () { + Future.delayed(const Duration(milliseconds: 1000), () { setState(() { changing = false; }); diff --git a/lib/ui/desktop/toolbar/setting/external_proxy.dart b/lib/ui/desktop/toolbar/setting/external_proxy.dart index dc2cbbc..4ea86de 100644 --- a/lib/ui/desktop/toolbar/setting/external_proxy.dart +++ b/lib/ui/desktop/toolbar/setting/external_proxy.dart @@ -1,6 +1,9 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:network_proxy/network/bin/configuration.dart'; +import 'package:network_proxy/network/host_port.dart'; class ExternalProxyDialog extends StatefulWidget { final Configuration configuration; @@ -20,7 +23,10 @@ class _ExternalProxyDialogState extends State { @override void initState() { super.initState(); - externalProxy = widget.configuration.externalProxy ?? ProxyInfo(); + externalProxy = ProxyInfo(); + if (widget.configuration.externalProxy != null) { + externalProxy = ProxyInfo.fromJson(widget.configuration.externalProxy!.toJson()); + } } @override @@ -35,31 +41,26 @@ class _ExternalProxyDialogState extends State { }, child: const Text("取消")), TextButton( - onPressed: () { + onPressed: () async { if (!formKey.currentState!.validate()) { return; } - widget.configuration.externalProxy = externalProxy; - widget.configuration.flushConfig(); - if (externalProxy.enable) { - - } - Navigator.of(context).pop(); + submit(); }, child: const Text("确定")) ], content: Form( key: formKey, child: Column(mainAxisSize: MainAxisSize.min, children: [ - const Text("注意:请将科学上网网站加入域名过滤黑名单。", style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500)), + const Text("如发现访问失败的外网请将加入域名过滤黑名单。", style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500)), const SizedBox(height: 10), Row(children: [ const Text("是否启用:"), Expanded( child: Switch( - value: externalProxy.enable, + value: externalProxy.enabled, onChanged: (val) { - setState(() => externalProxy.enable = val); + setState(() => externalProxy.enabled = val); }, )) ]), @@ -88,4 +89,43 @@ class _ExternalProxyDialogState extends State { ]), ]))); } + + submit() async { + bool setting = true; + if (externalProxy.enabled) { + try { + var socket = await Socket.connect(externalProxy.host, externalProxy.port!, timeout: const Duration(seconds: 1)); + socket.destroy(); + } on SocketException catch (_) { + setting = false; + + await showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text("外部代理连接失败"), + content: const Text('网络不通所有接口将会访问失败,是否继续设置外部代理。', style: TextStyle(fontSize: 12)), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("取消")), + TextButton( + onPressed: () { + setting = true; + Navigator.of(context).pop(); + }, + child: const Text("确定")) + ], + )); + } + } + + if (setting) { + widget.configuration.externalProxy = externalProxy; + widget.configuration.flushConfig(); + } + + if (context.mounted) Navigator.of(context).pop(); + } } diff --git a/lib/ui/desktop/toolbar/setting/setting.dart b/lib/ui/desktop/toolbar/setting/setting.dart index 0b09758..a93bf94 100644 --- a/lib/ui/desktop/toolbar/setting/setting.dart +++ b/lib/ui/desktop/toolbar/setting/setting.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/bin/server.dart'; -import 'package:network_proxy/network/util/system_proxy.dart'; import 'package:network_proxy/ui/desktop/toolbar/setting/external_proxy.dart'; import 'package:network_proxy/ui/desktop/toolbar/setting/request_rewrite.dart'; import 'package:network_proxy/ui/desktop/toolbar/setting/theme.dart'; @@ -27,7 +26,7 @@ class _SettingState extends State { @override void initState() { configuration = widget.proxyServer.configuration; - enableDesktopListenable = ValueNotifier(configuration.enableDesktop); + enableDesktopListenable = ValueNotifier(configuration.enableSystemProxy); super.initState(); } @@ -100,10 +99,10 @@ class _SettingState extends State { title: const Text("设置为系统代理"), visualDensity: const VisualDensity(horizontal: -4), dense: true, - value: configuration.enableDesktop, + value: configuration.enableSystemProxy, onChanged: (val) { - SystemProxy.setSystemProxyEnable(widget.proxyServer.port, val, widget.proxyServer.enableSsl); - configuration.enableDesktop = val; + widget.proxyServer.setSystemProxyEnable(val); + configuration.enableSystemProxy = val; enableDesktopListenable.value = !enableDesktopListenable.value; configuration.flushConfig(); }); diff --git a/lib/ui/launch/launch.dart b/lib/ui/launch/launch.dart index aa8623f..8af02b5 100644 --- a/lib/ui/launch/launch.dart +++ b/lib/ui/launch/launch.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/bin/server.dart'; +import 'package:network_proxy/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; class SocketLaunch extends StatefulWidget { @@ -31,6 +32,9 @@ class _SocketLaunchState extends State with WindowListener, Widget if (widget.startup) { start(); } + if (Platforms.isDesktop()) { + windowManager.setPreventClose(true); + } } @override @@ -42,8 +46,8 @@ class _SocketLaunchState extends State with WindowListener, Widget @override void onWindowClose() async { - print("onWindowClose"); await widget.proxyServer.stop(); + print("onWindowClose"); started = false; windowManager.destroy(); } diff --git a/lib/utils/ip.dart b/lib/utils/ip.dart index f4cfc38..56d1af1 100644 --- a/lib/utils/ip.dart +++ b/lib/utils/ip.dart @@ -1,14 +1,16 @@ import 'dart:io'; void main() { - NetworkInterface.list(type: InternetAddressType.IPv4).then((interfaces) => interfaces.forEach((interface) { - print(interface.name); - for (var address in interface.addresses) { - print(" ${address.address}"); - print(" ${address.host}"); - print(" ${address.type}"); - } - })); + NetworkInterface.list(type: InternetAddressType.IPv4).then((interfaces) { + for (var interface in interfaces) { + print(interface.name); + for (var address in interface.addresses) { + print(" ${address.address}"); + print(" ${address.host}"); + print(" ${address.type}"); + } + } + }); } String? ip; diff --git a/pubspec.lock b/pubspec.lock index 40639f4..83d21e1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -316,10 +316,10 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + sha256: bced5679c7df11190e1ddc35f3222c858f328fff85c3942e46e7f5589bf9eb84 url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.6" + version: "2.1.0" path_provider_windows: dependency: transitive description: @@ -396,18 +396,18 @@ packages: dependency: "direct main" description: name: share_plus - sha256: ed3fcea4f789ed95913328e629c0c53e69e80e08b6c24542f1b3576046c614e8 + sha256: "6cec740fa0943a826951223e76218df002804adb588235a8910dc3d6b0654e11" url: "https://pub.flutter-io.cn" source: hosted - version: "7.0.2" + version: "7.1.0" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981" + sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7" url: "https://pub.flutter-io.cn" source: hosted - version: "3.2.1" + version: "3.3.0" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index aa54b7f..d15255b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: qrscan: ^0.3.3 flutter_barcode_scanner: ^2.0.0 flutter_toastr: ^1.0.3 - share_plus: ^7.0.2 + share_plus: ^7.1.0 brotli: ^0.6.0 dev_dependencies: diff --git a/test/http_test.dart b/test/http_test.dart new file mode 100644 index 0000000..4429a2b --- /dev/null +++ b/test/http_test.dart @@ -0,0 +1,15 @@ +import 'dart:io'; + +main() async { + // await socketTest(); + await webTest(); +} + +webTest() async { + var httpClient = HttpClient(); + httpClient.findProxy = (uri) => "PROXY 127.0.0.1:7890"; + // httpClient.badCertificateCallback = (X509Certificate cert, String host, int port) => true; + var httpClientRequest = await httpClient.getUrl(Uri.parse("https://www.v2ex.com")); + var response = await httpClientRequest.close(); + print(response.headers); +} diff --git a/test/web_test.dart b/test/web_test.dart index 19aed8b..a73c798 100644 --- a/test/web_test.dart +++ b/test/web_test.dart @@ -1,27 +1,61 @@ - +import 'dart:async'; import 'dart:io'; import 'package:network_proxy/network/http/codec.dart'; import 'package:network_proxy/network/http/http.dart'; main() async { - var connect = await Socket.connect("127.0.0.1", 7890); - var httpRequest = HttpRequest(HttpMethod.connect, "https://www.baidu.com"); - var codec = HttpRequestCodec(); - connect.add(codec.encode(httpRequest)); - - - await connect.flush(); - var first = await connect.first; - print(String.fromCharCodes(first)); - await Future.delayed(const Duration(seconds: 1)); - httpRequest = HttpRequest(HttpMethod.get, "https://www.baidu.com"); - codec = HttpRequestCodec(); - connect.add(codec.encode(httpRequest)); - // var httpClient = HttpClient(); - // httpClient .findProxy = (uri) => "PROXY 127.0.0.1:7890"; - // httpClient.getUrl(Uri.parse("https://www.youtube.com:443")); - SecurityContext.defaultContext.allowLegacyUnsafeRenegotiation = true; - await SecureSocket.secure(connect); + await socketTest(); } +socketTest() async { + var task = await Socket.startConnect("127.0.0.1", 7890); + var socket = await task.socket; + if (socket.address.type != InternetAddressType.unix) { + socket.setOption(SocketOption.tcpNoDelay, true); + } + + Completer completer = Completer(); + StreamSubscription? subscription; + subscription = socket.listen((event) { + subscription!.pause(); + print(String.fromCharCodes(event)); + completer.complete(true); + }); + + String host = 'www.v2ex.com:443'; + + var httpRequest = HttpRequest(HttpMethod.connect, host); + httpRequest.headers.set('user-agent', 'Dart/3.0 (dart:io)'); + httpRequest.headers.set('accept-encoding', 'gzip'); + httpRequest.headers.set(HttpHeaders.hostHeader, host); + + var codec = HttpRequestCodec(); + print(String.fromCharCodes(codec.encode(httpRequest))); + socket.add(codec.encode(httpRequest)); + await socket.flush(); + + // subscription.resume(); + + await completer.future; + // await Future.delayed(const Duration(milliseconds: 1600)); + + var secureSocket = await SecureSocket.secure(socket, host: 'www.v2ex.com', onBadCertificate: (certificate) => true); + print("secureSocket"); + // await subscription.cancel(); + + completer = Completer(); + subscription = secureSocket.listen((event) { + subscription?.pause(); + print(String.fromCharCodes(event)); + completer.complete(true); + subscription?.resume(); + }); + + httpRequest = HttpRequest(HttpMethod.get, "/"); + httpRequest.headers.set(HttpHeaders.hostHeader, host); + + secureSocket.add(codec.encode(httpRequest)); + await secureSocket.flush(); + await completer.future; +}