外部代理设置,抓包可以同时访问外网,退出时自动还原成外部代理

This commit is contained in:
wanghongen
2023-08-02 00:57:06 +08:00
parent 7acf8c86db
commit cdf7fcd84a
21 changed files with 438 additions and 278 deletions

View File

@@ -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<String, dynamic> json) {
enable = json['enable'] == true;
host = json['host'];
port = json['port'];
}
Map<String, dynamic> toJson() {
return {
'enable': enable,
'host': host,
'port': port,
};
}
}

View File

@@ -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<Server?> 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());

View File

@@ -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<void> 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<Uint8List> {
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<Uint8List> {
@override
void channelActive(Channel channel) {
_handler.channelActive(channel);
handler.channelActive(channel);
}
/// 转发请求
@@ -152,7 +160,7 @@ class ChannelPipeline extends ChannelHandler<Uint8List> {
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<Uint8List> {
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<Object> {
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<ServerSocket> bind(int port) async {
serverSocket = await ServerSocket.bind(InternetAddress.anyIPv4, port);
serverSocket.listen((socket) {
listen(socket);
});
isRunning = true;
return serverSocket;
}
Future<ServerSocket> stop() async {
if (!isRunning) return serverSocket;
isRunning = false;
await serverSocket.close();
return serverSocket;
}
}
class Client extends Network {
Future<Channel> 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<Channel> secureConnect(HostAndPort hostAndPort) async {
return SecureSocket.connect(hostAndPort.host, hostAndPort.port,
timeout: const Duration(seconds: 3), onBadCertificate: (certificate) => true).then((socket) => listen(socket));
}
}

View File

@@ -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<HttpRequest> {
/// 转发请求
Future<void> 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<HttpRequest> {
if (!HostFilter.filter(httpRequest.hostAndPort?.host)) {
listener?.onRequest(channel, httpRequest);
}
//实现抓包代理转发
await remoteChannel.write(httpRequest);
}
}
@@ -139,27 +139,23 @@ class HttpChannelHandler extends ChannelHandler<HttpRequest> {
//远程转发
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<HttpRequest> {
/// http响应代理
class HttpResponseProxyHandler extends ChannelHandler<HttpResponse> {
//客户端的连接
final Channel clientChannel;
EventListener? listener;
@@ -176,10 +173,10 @@ class HttpResponseProxyHandler extends ChannelHandler<HttpResponse> {
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<HttpResponse> {
if (!HostFilter.filter(msg.request?.hostAndPort?.host)) {
listener?.onResponse(clientChannel, msg);
}
//发送给客户端
clientChannel.write(msg);
await clientChannel.write(msg);
}
@override

View File

@@ -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<String, dynamic> json) {
enabled = json['enabled'] == true;
host = json['host'];
port = json['port'];
}
Map<String, dynamic> toJson() {
return {
'enabled': enabled,
'host': host,
'port': port,
};
}
@override
String toString() {
return '{enabled: $enabled, host: $host, port: $port}';
}
}

View File

@@ -43,7 +43,7 @@ abstract interface class Encoder<T> {
/// 编解码器
abstract class Codec<T> implements Decoder<T>, Encoder<T> {
static const int defaultMaxInitialLineLength = 10240;
static const int maxBodyLength = 1024000;
static const int maxBodyLength = 4096000;
}
/// http编解码

View File

@@ -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);

View File

@@ -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<Channel> rawConnect(HostAndPort hostAndPort, ChannelHandler handler) async {
static Future<Channel> startConnect(HostAndPort hostAndPort, ChannelHandler handler) async {
var client = Client()
..initChannel((channel) => channel.pipeline.handle(HttpResponseCodec(), HttpRequestCodec(), handler));
return client.connect(hostAndPort);
}
///代理建立连接
static Future<Channel> 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<Channel> 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<HttpResponse> {
@override
void channelRead(Channel channel, HttpResponse msg) {
// log.i("[${channel.id}] Response ${msg.bodyAsString}");
// log.i("[${channel.id}] Response $msg");
_completer.complete(msg);
}

138
lib/network/network.dart Normal file
View File

@@ -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<ServerSocket> bind(int port) async {
serverSocket = await ServerSocket.bind(InternetAddress.anyIPv4, port);
serverSocket.listen((socket) {
listen(socket);
});
isRunning = true;
return serverSocket;
}
Future<ServerSocket> stop() async {
if (!isRunning) return serverSocket;
isRunning = false;
await serverSocket.close();
return serverSocket;
}
}
class Client extends Network {
Future<Channel> 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<Channel> secureConnect(HostAndPort hostAndPort) async {
return SecureSocket.connect(hostAndPort.host, hostAndPort.port,
timeout: const Duration(seconds: 3), onBadCertificate: (certificate) => true).then((socket) => listen(socket));
}
}

View File

@@ -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,

View File

@@ -7,23 +7,26 @@ class SystemProxy {
static String? _hardwarePort;
/// 设置系统代理
static void setSystemProxy(int port, bool sslSetting) async {
static Future<void> 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<void> 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<bool> _setProxyServerWindows(int proxyPort, bool sslSetting) async {
ProxyManager manager = ProxyManager();
await manager.setAsSystemProxy(ProxyTypes.http, "127.0.0.1", proxyPort);
static Future<bool> _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',

View File

@@ -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 = <int>[];
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':

View File

@@ -38,7 +38,7 @@ class DomainWidgetState extends State<DomainWidget> {
changeState() {
if (!changing) {
changing = true;
Future.delayed(const Duration(milliseconds: 1500), () {
Future.delayed(const Duration(milliseconds: 1000), () {
setState(() {
changing = false;
});

View File

@@ -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<ExternalProxyDialog> {
@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<ExternalProxyDialog> {
},
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<ExternalProxyDialog> {
]),
])));
}
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();
}
}

View File

@@ -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<Setting> {
@override
void initState() {
configuration = widget.proxyServer.configuration;
enableDesktopListenable = ValueNotifier<bool>(configuration.enableDesktop);
enableDesktopListenable = ValueNotifier<bool>(configuration.enableSystemProxy);
super.initState();
}
@@ -100,10 +99,10 @@ class _SettingState extends State<Setting> {
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();
});

View File

@@ -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<SocketLaunch> with WindowListener, Widget
if (widget.startup) {
start();
}
if (Platforms.isDesktop()) {
windowManager.setPreventClose(true);
}
}
@override
@@ -42,8 +46,8 @@ class _SocketLaunchState extends State<SocketLaunch> with WindowListener, Widget
@override
void onWindowClose() async {
print("onWindowClose");
await widget.proxyServer.stop();
print("onWindowClose");
started = false;
windowManager.destroy();
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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:

15
test/http_test.dart Normal file
View File

@@ -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);
}

View File

@@ -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<bool> completer = Completer<bool>();
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<bool>();
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;
}