From 2f2e5b405b491335b29243b5b9dccd2334eda548 Mon Sep 17 00:00:00 2001 From: wanghongen Date: Mon, 20 Nov 2023 01:52:55 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=89=E5=8D=93=E6=8A=93flutter=E8=AF=B7?= =?UTF-8?q?=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/native/vpn.dart | 5 +++ lib/network/bin/server.dart | 2 +- lib/network/channel.dart | 5 ++- lib/network/handler.dart | 9 ++-- lib/network/host_port.dart | 5 +-- lib/network/network.dart | 14 +++--- lib/network/util/tls.dart | 52 +++++++++++++++++++++++ lib/ui/mobile/request/history.dart | 4 +- lib/ui/mobile/request/request_editor.dart | 6 ++- 9 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 lib/network/util/tls.dart diff --git a/lib/native/vpn.dart b/lib/native/vpn.dart index f421d53..71d49e0 100644 --- a/lib/native/vpn.dart +++ b/lib/native/vpn.dart @@ -3,16 +3,21 @@ import 'package:flutter/services.dart'; class Vpn { static const MethodChannel proxyVpnChannel = MethodChannel('com.proxy/proxyVpn'); + static bool isVpnStarted = false; //vpn是否已经启动 + static startVpn(String host, int port, [List? appList]) { proxyVpnChannel.invokeMethod("startVpn", {"proxyHost": host, "proxyPort": port, "allowApps": appList}); + isVpnStarted = true; } static stopVpn() { proxyVpnChannel.invokeMethod("stopVpn"); + isVpnStarted = false; } //重启vpn static restartVpn(String host, int port, [List? appList]) { proxyVpnChannel.invokeMethod("restartVpn", {"proxyHost": host, "proxyPort": port, "allowApps": appList}); + isVpnStarted = true; } } diff --git a/lib/network/bin/server.dart b/lib/network/bin/server.dart index 71786ec..60eefc6 100644 --- a/lib/network/bin/server.dart +++ b/lib/network/bin/server.dart @@ -70,7 +70,7 @@ class ProxyServer { channel.pipeline.handle( HttpRequestCodec(), HttpResponseCodec(), - HttpChannelHandler( + HttpProxyChannelHandler( listener: CombinedEventListener(listeners), requestRewrites: configuration.requestRewrites)); }); diff --git a/lib/network/channel.dart b/lib/network/channel.dart index dfd660a..bf2c033 100644 --- a/lib/network/channel.dart +++ b/lib/network/channel.dart @@ -83,6 +83,9 @@ class Channel { pipeline.listen(this); } + ///是否是ssl链接 + bool get isSsl => _socket is SecureSocket; + Future write(Object obj) async { if (isClosed) { logger.w("[$id] channel is closed"); @@ -214,7 +217,7 @@ class ChannelPipeline extends ChannelHandler { buffer.clear(); if (data is HttpRequest) { - data.hostAndPort = channel.getAttribute(AttributeKeys.host) ?? getHostAndPort(data); + data.hostAndPort = channel.getAttribute(AttributeKeys.host) ?? getHostAndPort(data, ssl: channel.isSsl); if (data.headers.host != null && data.headers.host?.contains(":") == false) { data.hostAndPort?.host = data.headers.host!; } diff --git a/lib/network/handler.dart b/lib/network/handler.dart index 2a7b1b6..1d3649a 100644 --- a/lib/network/handler.dart +++ b/lib/network/handler.dart @@ -40,11 +40,11 @@ abstract class EventListener { } /// http请求处理器 -class HttpChannelHandler extends ChannelHandler { +class HttpProxyChannelHandler extends ChannelHandler { EventListener? listener; RequestRewrites? requestRewrites; - HttpChannelHandler({this.listener, this.requestRewrites}); + HttpProxyChannelHandler({this.listener, this.requestRewrites}); @override void channelRead(Channel channel, HttpRequest msg) async { @@ -202,7 +202,7 @@ class HttpChannelHandler extends ChannelHandler { return remoteChannel; } - var hostAndPort = getHostAndPort(httpRequest); + var hostAndPort = httpRequest.hostAndPort ?? getHostAndPort(httpRequest); clientChannel.putAttribute(AttributeKeys.host, hostAndPort); var proxyHandler = HttpResponseProxyHandler(clientChannel, listener: listener, requestRewrites: requestRewrites); @@ -226,6 +226,9 @@ class HttpChannelHandler extends ChannelHandler { if (httpRequest.method == HttpMethod.connect) { await clientChannel.write( HttpResponse(HttpStatus.ok.reason('Connection established'), protocolVersion: httpRequest.protocolVersion)); + } else if (clientChannel.isSsl) { + proxyChannel.secureSocket = await SecureSocket.secure(proxyChannel.socket, + host: hostAndPort.host, onBadCertificate: (certificate) => true); } return proxyChannel; } diff --git a/lib/network/host_port.dart b/lib/network/host_port.dart index c86f30f..d170e95 100644 --- a/lib/network/host_port.dart +++ b/lib/network/host_port.dart @@ -18,14 +18,13 @@ import 'package:network_proxy/network/http/http.dart'; import 'package:network_proxy/network/http/http_headers.dart'; /// 获取主机和端口 -HostAndPort getHostAndPort(HttpRequest request) { +HostAndPort getHostAndPort(HttpRequest request, {bool? ssl}) { String requestUri = request.uri; //有些请求直接是路径 /xxx, 从header取host if (request.uri.startsWith("/")) { requestUri = request.headers.get(HttpHeaders.HOST)!; } - - return HostAndPort.of(requestUri); + return HostAndPort.of(requestUri, ssl: ssl); } class HostAndPort { diff --git a/lib/network/network.dart b/lib/network/network.dart index e1517d9..a38d226 100644 --- a/lib/network/network.dart +++ b/lib/network/network.dart @@ -24,6 +24,7 @@ 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 'package:network_proxy/network/util/tls.dart'; import 'package:network_proxy/utils/platform.dart'; import 'host_port.dart'; @@ -70,12 +71,11 @@ class Network { } //ssl握手 - if (hostAndPort?.isSsl() == true || (data.length > 3 && data.first == 0x16 && data[1] == 0x03 && data[2] == 0x01)) { + if (hostAndPort?.isSsl() == true || (data.length > 2 && data.first == 0x16 && data[1] == 0x03)) { if (hostAndPort?.scheme == HostAndPort.httpScheme) { hostAndPort?.scheme = HostAndPort.httpsScheme; } - - ssl(channel, hostAndPort!, data); + ssl(channel, hostAndPort, data); return; } @@ -83,16 +83,18 @@ class Network { } /// ssl握手 - void ssl(Channel channel, HostAndPort hostAndPort, Uint8List data) async { + void ssl(Channel channel, HostAndPort? hostAndPort, Uint8List data) async { try { Channel? remoteChannel = channel.getAttribute(channel.id); if (remoteChannel != null) { remoteChannel.secureSocket = await SecureSocket.secure(remoteChannel.socket, - host: hostAndPort.host, onBadCertificate: (certificate) => true); + host: hostAndPort?.host, onBadCertificate: (certificate) => true); } + String? host = hostAndPort?.host; + host ??= TLS.getDomain(data); //ssl自签证书 - var certificate = await CertificateManager.getCertificateContext(hostAndPort.host); + var certificate = await CertificateManager.getCertificateContext(host!); //服务端等待客户端ssl握手 channel.secureSocket = await SecureSocket.secureServer(channel.socket, certificate, bufferedData: data); } catch (error, trace) { diff --git a/lib/network/util/tls.dart b/lib/network/util/tls.dart new file mode 100644 index 0000000..908577c --- /dev/null +++ b/lib/network/util/tls.dart @@ -0,0 +1,52 @@ +import 'dart:typed_data'; + +class TLS { + ///从TLS Client Hello 解析域名 + static String? getDomain(Uint8List data) { + try { + int sessionLength = data[43]; + int pos = 44 + sessionLength; + if (data.length < pos + 2) return null; + + int cipherSuitesLength = data.buffer.asByteData().getUint16(pos); + pos += 2 + cipherSuitesLength; + if (data.length < pos + 1) return null; + + int compressionMethodsLength = data[pos]; + pos += 1 + compressionMethodsLength; + if (data.length < pos + 2) return null; + + int extensionsLength = data.buffer.asByteData().getUint16(pos); + pos += 2; + if (data.length < pos + extensionsLength) return null; + + int end = pos + extensionsLength; + while (pos + 4 <= end) { + int extensionType = data.buffer.asByteData().getUint16(pos); + int extensionLength = data.buffer.asByteData().getUint16(pos + 2); + pos += 4; + + if (extensionType == 0 /* server_name */) { + if (pos + 5 > end) return null; + int serverNameListLength = data.buffer.asByteData().getUint16(pos); + pos += 2; + if (pos + serverNameListLength > end) return null; + + int serverNameType = data[pos]; + int serverNameLength = data.buffer.asByteData().getUint16(pos + 1); + pos += 3; + if (serverNameType != 0 /* host_name */) return null; + if (pos + serverNameLength > end) return null; + + return String.fromCharCodes(data.sublist(pos, pos + serverNameLength)); + } else { + pos += extensionLength; + } + } + } catch (_) { + // Ignore errors, just return null + } + + return null; + } +} diff --git a/lib/ui/mobile/request/history.dart b/lib/ui/mobile/request/history.dart index 53b29f3..4b75bbd 100644 --- a/lib/ui/mobile/request/history.dart +++ b/lib/ui/mobile/request/history.dart @@ -23,7 +23,7 @@ class MobileHistory extends StatefulWidget { final ProxyServer proxyServer; final GlobalKey requestStateKey; - const MobileHistory({Key? key, required this.proxyServer, required this.requestStateKey}) : super(key: key); + const MobileHistory({super.key, required this.proxyServer, required this.requestStateKey}); @override State createState() { @@ -92,7 +92,7 @@ class _MobileHistoryState extends State { //导入har import(HistoryStorage storage) async { - const XTypeGroup typeGroup = XTypeGroup(label: 'Har', extensions: ['har']); + const XTypeGroup typeGroup = XTypeGroup(label: 'har', extensions: ['har'], uniformTypeIdentifiers: ["public.item"]); final XFile? file = await openFile(acceptedTypeGroups: [typeGroup]); if (file == null) { return; diff --git a/lib/ui/mobile/request/request_editor.dart b/lib/ui/mobile/request/request_editor.dart index 9b81624..0563008 100644 --- a/lib/ui/mobile/request/request_editor.dart +++ b/lib/ui/mobile/request/request_editor.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_toastr/flutter_toastr.dart'; +import 'package:network_proxy/native/vpn.dart'; import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/network/host_port.dart'; import 'package:network_proxy/network/http/http.dart'; @@ -123,11 +124,12 @@ class RequestEditorState extends State with SingleTickerPro var headers = requestKey.currentState?.getHeaders(); var requestBody = requestKey.currentState?.getBody(); - HttpRequest request = HttpRequest(HttpMethod.valueOf(currentState.requestMethod), Uri.encodeFull(currentState.requestUrl)); + HttpRequest request = + HttpRequest(HttpMethod.valueOf(currentState.requestMethod), Uri.encodeFull(currentState.requestUrl)); request.headers.addAll(headers); request.body = requestBody == null ? null : utf8.encode(requestBody); - var proxyInfo = widget.proxyServer?.isRunning == true ? ProxyInfo.of("127.0.0.1", widget.proxyServer!.port) : null; + var proxyInfo = Vpn.isVpnStarted ? ProxyInfo.of("127.0.0.1", widget.proxyServer!.port) : null; HttpClients.proxyRequest(proxyInfo: proxyInfo, request).then((response) { FlutterToastr.show('请求成功', context); this.response = response;