This commit is contained in:
wanghongenpin
2025-01-15 01:36:00 +08:00
parent 12ab378ced
commit 827e6fe0d8
12 changed files with 255 additions and 38 deletions

View File

@@ -37,6 +37,9 @@ class Configuration {
//代理忽略域名
String proxyPassDomains = SystemProxy.proxyPassDomains;
//enabled socks5 proxy
bool enableSocks5 = true;
//外部代理
ProxyInfo? externalProxy;
@@ -84,6 +87,7 @@ class Configuration {
enableSsl = config['enableSsl'] == true;
startup = config['startup'] ?? Platforms.isDesktop();
enableSystemProxy = config['enableSystemProxy'] ?? (config['enableDesktop'] ?? true);
enableSocks5 = config['enableSocks5'] ?? true;
proxyPassDomains = config['proxyPassDomains'] ?? SystemProxy.proxyPassDomains;
historyCacheTime = config['historyCacheTime'] ?? 0;
if (config['externalProxy'] != null) {
@@ -136,6 +140,7 @@ class Configuration {
'enableSsl': enableSsl,
'startup': startup,
'enableSystemProxy': enableSystemProxy,
'enableSocks5': enableSocks5,
'proxyPassDomains': proxyPassDomains,
'externalProxy': externalProxy?.toJson(),
'appWhitelist': appWhitelist,

View File

@@ -107,7 +107,7 @@ class Channel {
bool get isSsl => _socket is SecureSocket;
Future<void> write(Object obj) async {
var data = pipeline._encoder.encode(obj);
var data = pipeline.encoder.encode(obj);
await writeBytes(data);
}
@@ -251,18 +251,22 @@ class ChannelContext {
}
class ChannelPipeline extends ChannelHandler<Uint8List> {
late Decoder _decoder;
late Encoder _encoder;
late Decoder decoder;
late Encoder encoder;
late ChannelHandler handler;
final ByteBuf buffer = ByteBuf();
handle(Decoder decoder, Encoder encoder, ChannelHandler handler) {
_encoder = encoder;
_decoder = decoder;
this.encoder = encoder;
this.decoder = decoder;
this.handler = handler;
}
channelHandle(Codec codec, ChannelHandler handler) {
handle(codec, codec, handler);
}
/// 监听
void listen(Channel channel, ChannelContext channelContext) {
buffer.clear();
@@ -297,8 +301,8 @@ class ChannelPipeline extends ChannelHandler<Uint8List> {
/// 转发请求
void relay(ChannelContext channelContext, Channel clientChannel, Channel remoteChannel) {
var rawCodec = RawCodec();
clientChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(remoteChannel));
remoteChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(clientChannel));
clientChannel.pipeline.channelHandle(rawCodec, RelayHandler(remoteChannel));
remoteChannel.pipeline.channelHandle(rawCodec, RelayHandler(clientChannel));
var body = buffer.bytes;
buffer.clear();
@@ -326,7 +330,7 @@ class ChannelPipeline extends ChannelHandler<Uint8List> {
return;
}
var decodeResult = _decoder.decode(channelContext, buffer);
var decodeResult = decoder.decode(channelContext, buffer);
if (!decodeResult.isDone) {
return;
}
@@ -376,7 +380,8 @@ class ChannelPipeline extends ChannelHandler<Uint8List> {
//websocket协议
if (data is HttpResponse && data.isWebSocket && remoteChannel != null) {
data.request?.response = data;
channelContext.host = channelContext.host?.copyWith(scheme: channel.isSsl ? HostAndPort.wssScheme : HostAndPort.wsScheme);
channelContext.host =
channelContext.host?.copyWith(scheme: channel.isSsl ? HostAndPort.wssScheme : HostAndPort.wsScheme);
channelContext.currentRequest?.hostAndPort = channelContext.host;
logger.d("webSocket ${data.request?.hostAndPort}");
@@ -385,8 +390,8 @@ class ChannelPipeline extends ChannelHandler<Uint8List> {
channelContext.listener?.onResponse(channelContext, data);
var rawCodec = RawCodec();
channel.pipeline.handle(rawCodec, rawCodec, WebSocketChannelHandler(remoteChannel, data));
remoteChannel.pipeline.handle(rawCodec, rawCodec, WebSocketChannelHandler(channel, data.request!));
channel.pipeline.channelHandle(rawCodec, WebSocketChannelHandler(remoteChannel, data));
remoteChannel.pipeline.channelHandle(rawCodec, WebSocketChannelHandler(channel, data.request!));
return;
}
@@ -408,10 +413,10 @@ class ChannelPipeline extends ChannelHandler<Uint8List> {
}
}
class RawCodec extends Codec<dynamic> {
class RawCodec extends Codec<Uint8List, List<int>> {
@override
DecoderResult<dynamic> decode(ChannelContext channelContext, ByteBuf byteBuf, {bool resolveBody = true}) {
var decoderResult = DecoderResult()..data = byteBuf.readAvailableBytes();
DecoderResult<Uint8List> decode(ChannelContext channelContext, ByteBuf byteBuf, {bool resolveBody = true}) {
var decoderResult = DecoderResult<Uint8List>()..data = byteBuf.readAvailableBytes();
return decoderResult;
}

View File

@@ -207,6 +207,12 @@ class HttpProxyChannelHandler extends ChannelHandler<HttpRequest> {
}
HostAndPort remoteAddress = hostAndPort;
final ProxyInfo? socksProxy = channelContext.getAttribute(AttributeKeys.socks5Proxy);
if (socksProxy != null) {
remoteAddress = hostAndPort.copyWith(host: socksProxy.host, port: socksProxy.port!);
}
for (var interceptor in interceptors) {
remoteAddress = await interceptor.preConnect(remoteAddress);
}

View File

@@ -14,7 +14,6 @@
* limitations under the License.
*/
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
@@ -71,13 +70,13 @@ abstract interface class Encoder<T> {
}
/// 编解码器
abstract class Codec<T> implements Decoder<T>, Encoder<T> {
abstract class Codec<D, E> implements Decoder<D>, Encoder<E> {
static const int defaultMaxInitialLineLength = 1024000; // 1M
static const int maxBodyLength = 4096000; // 4M
}
/// http编解码
abstract class HttpCodec<T extends HttpMessage> implements Codec<T> {
abstract class HttpCodec<T extends HttpMessage> implements Codec<T, T> {
final HttpParse _httpParse = HttpParse();
Http2Codec<T>? _h2Codec;
State _state = State.readInitial;
@@ -266,3 +265,33 @@ class HttpResponseCodec extends HttpCodec<HttpResponse> {
buffer.addByte(HttpConstants.lf);
}
}
class HttpServerCodec extends Codec<HttpRequest, HttpResponse> {
HttpRequestCodec requestCodec = HttpRequestCodec();
HttpResponseCodec responseCodec = HttpResponseCodec();
@override
DecoderResult<HttpRequest> decode(ChannelContext channelContext, ByteBuf byteBuf) {
return requestCodec.decode(channelContext, byteBuf);
}
@override
List<int> encode(HttpResponse data) {
return responseCodec.encode(data);
}
}
class HttpClientCodec extends Codec<HttpResponse, HttpRequest> {
HttpRequestCodec requestCodec = HttpRequestCodec();
HttpResponseCodec responseCodec = HttpResponseCodec();
@override
DecoderResult<HttpResponse> decode(ChannelContext channelContext, ByteBuf byteBuf) {
return responseCodec.decode(channelContext, byteBuf);
}
@override
List<int> encode(HttpRequest data) {
return requestCodec.encode(data);
}
}

View File

@@ -27,7 +27,7 @@ import 'package:proxypin/network/util/byte_buf.dart';
import 'frame.dart';
/// http编解码
abstract class Http2Codec<T extends HttpMessage> implements Codec<T> {
abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
static const maxFrameSize = 16384;
static final List<int> connectionPrefacePRI = "PRI * HTTP/2.0".codeUnits;

View File

@@ -32,7 +32,7 @@ class HttpClients {
static Future<Channel> startConnect(
HostAndPort hostAndPort, ChannelHandler handler, ChannelContext channelContext) async {
var client = Client()
..initChannel((channel) => channel.pipeline.handle(HttpResponseCodec(), HttpRequestCodec(), handler));
..initChannel((channel) => channel.pipeline.channelHandle(HttpClientCodec(), handler));
return client.connect(hostAndPort, channelContext);
}
@@ -41,7 +41,7 @@ class HttpClients {
static Future<Channel> proxyConnect(HostAndPort hostAndPort, ChannelHandler handler, ChannelContext channelContext,
{ProxyInfo? proxyInfo}) async {
var client = Client()
..initChannel((channel) => channel.pipeline.handle(HttpResponseCodec(), HttpRequestCodec(), handler));
..initChannel((channel) => channel.pipeline.channelHandle(HttpClientCodec(), handler));
if (proxyInfo == null) {
var proxyTypes = hostAndPort.isSsl() ? ProxyTypes.https : ProxyTypes.http;

View File

@@ -22,6 +22,7 @@ import 'package:proxypin/network/bin/configuration.dart';
import 'package:proxypin/network/channel.dart';
import 'package:proxypin/network/components/host_filter.dart';
import 'package:proxypin/network/handler.dart';
import 'package:proxypin/network/socksx/socks5.dart';
import 'package:proxypin/network/util/attribute_keys.dart';
import 'package:proxypin/network/util/crts.dart';
import 'package:proxypin/network/util/logger.dart';
@@ -54,8 +55,8 @@ abstract class Network {
/// 转发请求
void relay(Channel clientChannel, Channel remoteChannel) {
var rawCodec = RawCodec();
clientChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(remoteChannel));
remoteChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(clientChannel));
clientChannel.pipeline.channelHandle(rawCodec, RelayHandler(remoteChannel));
remoteChannel.pipeline.channelHandle(rawCodec, RelayHandler(clientChannel));
}
}
@@ -123,6 +124,12 @@ class Server extends Network {
return;
}
//socks5
if (configuration.enableSocks5 && Socks5.isSocks5(data) && channel.pipeline.handler is! SocksServerHandler) {
channel.pipeline.channelHandle(
RawCodec(), SocksServerHandler(channel.pipeline.decoder, channel.pipeline.encoder, channel.pipeline.handler));
}
channel.pipeline.channelRead(channelContext, channel, data);
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2024 Hongen Wang All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'dart:typed_data';
import 'package:proxypin/network/channel.dart';
import 'package:proxypin/network/http/codec.dart';
import 'package:proxypin/network/util/attribute_keys.dart';
import 'package:proxypin/network/util/logger.dart';
import '../host_port.dart';
/// @author wanghongen
class Socks5 {
static const int version = 5;
static const int methodNoAuth = 0;
static const int methodNoAcceptable = 0xff;
static const int cmdConnect = 1;
static const int atypIpv4 = 1;
static const int repSuccess = 0;
static const int repCommandNotSupported = 7;
static const int repAddressTypeNotSupported = 8;
static const int repSocks5ServerAtypIpv4 = 0x01;
static const int repSocks5ServerAtypDomain = 0x03;
static const int repSocks5ServerAtypIpv6 = 0x04;
static bool isSocks5(Uint8List data) {
return data.length > 2 && data[0] == version;
}
}
///Detects the version of the current SOCKS connection and initializes the pipeline with Socks5InitialRequestDecoder.
class SocksServerHandler extends ChannelHandler<Uint8List> {
late Decoder originalDecoder;
late Encoder originalEncoder;
final ChannelHandler originalHandler;
SocksState socksState = SocksState.init;
SocksServerHandler(this.originalDecoder, this.originalEncoder, this.originalHandler);
@override
void channelRead(ChannelContext channelContext, Channel channel, Uint8List msg) async {
int idx = 0;
final int version = msg[idx++];
if (version != Socks5.version) {
await channel.writeBytes(Uint8List.fromList([Socks5.version, Socks5.methodNoAcceptable]));
channel.pipeline.exceptionCaught(channelContext, channel, Exception('Unsupported SOCKS version: $version'));
return;
}
if (socksState == SocksState.init) {
//no auth
await channel.writeBytes(Uint8List.fromList([Socks5.version, Socks5.methodNoAuth]));
socksState = SocksState.connect;
return;
}
if (socksState == SocksState.connect) {
final int cmd = msg[idx++];
if (cmd != Socks5.cmdConnect) {
var out = encodeCommandResponse(Socks5.repCommandNotSupported);
await channel.writeBytes(out);
channel.pipeline.exceptionCaught(channelContext, channel, Exception('Unsupported SOCKS cmd: $cmd'));
return;
}
//skip RSV
idx++;
final int dstAddrType = msg[idx++];
if (dstAddrType != Socks5.atypIpv4) {
var out = encodeCommandResponse(Socks5.repAddressTypeNotSupported);
await channel.writeBytes(out);
channel.pipeline.exceptionCaught(channelContext, channel, Exception('Unsupported SOCKS atyp: $dstAddrType'));
return;
}
final host = '${msg[idx++]}.${msg[idx++]}.${msg[idx++]}.${msg[idx++]}';
final int port = msg[idx++] << 8 | msg[idx++];
final proxyInfo = ProxyInfo.of(host, port);
logger.d('Socks5 connect ${proxyInfo.host}:${proxyInfo.port}');
channelContext.putAttribute(AttributeKeys.socks5Proxy, proxyInfo);
final out = encodeCommandResponse(Socks5.repSuccess, bndAddrType: Socks5.repSocks5ServerAtypIpv4);
await channel.writeBytes(out);
channel.pipeline.handle(originalDecoder, originalEncoder, originalHandler);
socksState = SocksState.connected;
return;
}
}
Uint8List encodeCommandResponse(int status, {int bndAddrType = 0, String? bndAddr, int bndPort = 0}) {
var out = BytesBuilder();
out.addByte(Socks5.version);
out.addByte(status);
out.addByte(0x00); //RSV
out.addByte(bndAddrType);
if (bndAddr != null) {
out.add(Int8List.fromList(bndAddr.split('.').map((e) => int.parse(e)).toList()));
} else {
out.add(Int8List.fromList([0, 0, 0, 0]));
}
out.addByte(bndPort >> 8);
out.addByte(bndPort & 0xff);
return out.takeBytes();
}
}
enum SocksState {
init,
auth,
connect,
connected,
}

View File

@@ -7,5 +7,6 @@ interface class AttributeKeys {
static const String request = "REQUEST";
static const String remote = "REMOTE";
static const String proxyInfo = "PROXY_INFO";
static const String socks5Proxy = "SOCKS5_PROXY";
static const String processInfo = "PROCESS_INFO";
}

View File

@@ -23,6 +23,7 @@ import 'package:proxypin/network/components/manager/hosts_manager.dart';
import 'package:proxypin/network/components/manager/request_block_manager.dart';
import 'package:proxypin/network/util/system_proxy.dart';
import 'package:proxypin/ui/component/multi_window.dart';
import 'package:proxypin/ui/component/widgets.dart';
import 'package:proxypin/ui/desktop/toolbar/setting/external_proxy.dart';
import 'package:proxypin/ui/desktop/toolbar/setting/hosts.dart';
import 'package:proxypin/ui/desktop/toolbar/setting/request_block.dart';
@@ -180,6 +181,21 @@ class _ProxyMenuState extends State<_ProxyMenu> {
const Divider(thickness: 0.3, height: 8),
setSystemProxy(),
const Divider(thickness: 0.3, height: 8),
Row(children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 15),
child: Text("SOCKS5", style: const TextStyle(fontSize: 14)))),
SwitchWidget(
value: configuration.enableSocks5,
scale: 0.75,
onChanged: (val) {
configuration.enableSocks5 = val;
changed = true;
}),
SizedBox(width: 10)
]),
const Divider(thickness: 0.3, height: 8),
const SizedBox(height: 3),
Padding(
padding: const EdgeInsets.only(left: 15),
@@ -225,22 +241,23 @@ class _ProxyMenuState extends State<_ProxyMenu> {
///设置系统代理
Widget setSystemProxy() {
return Row(children: [
Padding(
padding: const EdgeInsets.only(left: 15),
child: Text(localizations.systemProxy, style: const TextStyle(fontSize: 14))),
Expanded(
child: Transform.scale(
scale: 0.8,
child: Switch(
hoverColor: Colors.transparent,
value: configuration.enableSystemProxy,
onChanged: (val) {
widget.proxyServer.setSystemProxyEnable(val);
configuration.enableSystemProxy = val;
setState(() {
changed = true;
});
})))
child: Padding(
padding: const EdgeInsets.only(left: 15, right: 20),
child: Text(localizations.systemProxy, style: const TextStyle(fontSize: 14)))),
Transform.scale(
scale: 0.75,
child: Switch(
hoverColor: Colors.transparent,
value: configuration.enableSystemProxy,
onChanged: (val) {
widget.proxyServer.setSystemProxyEnable(val);
configuration.enableSystemProxy = val;
setState(() {
changed = true;
});
})),
SizedBox(width: 10)
]);
}
}

View File

@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:proxypin/network/bin/configuration.dart';
import 'package:proxypin/network/bin/server.dart';
import 'package:proxypin/network/util/logger.dart';
import 'package:proxypin/ui/component/widgets.dart';
@@ -25,6 +26,7 @@ class Preference extends StatefulWidget {
class _PreferenceState extends State<Preference> {
late ProxyServer proxyServer;
late Configuration configuration;
late AppConfiguration appConfiguration;
final memoryCleanupController = TextEditingController();
@@ -34,6 +36,7 @@ class _PreferenceState extends State<Preference> {
void initState() {
super.initState();
proxyServer = widget.proxyServer;
configuration = widget.proxyServer.configuration;
appConfiguration = widget.appConfiguration;
if (!memoryCleanupList.contains(appConfiguration.memoryCleanupThreshold)) {
@@ -61,6 +64,15 @@ class _PreferenceState extends State<Preference> {
proxyServer: proxyServer,
title: '${localizations.proxy}${isEn ? ' ' : ''}${localizations.port}',
textStyle: const TextStyle(fontSize: 16)),
ListTile(
title: Text("SOCKS5"),
trailing: SwitchWidget(
value: configuration.enableSocks5,
scale: 0.8,
onChanged: (value) {
configuration.enableSocks5 = value;
proxyServer.configuration.flushConfig();
})),
ListTile(
title: Text(localizations.externalProxy),
trailing: const Icon(Icons.keyboard_arrow_right),

View File

@@ -118,7 +118,7 @@ class _MobileScriptState extends State<MobileScript> {
//导入js
import() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['json']);
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.any);
if (result == null || result.files.isEmpty) {
return;
}