修正VMess 和 VLESS TLS配置问题,并且修改了切换节点的逻辑
(cherry picked from commit 8c34c2b0d31ee37a566de6dcb3ec62b7bb0a7222)
This commit is contained in:
parent
b79ce2d15a
commit
c5715f77e2
@ -243,6 +243,111 @@ class KROutboundItem {
|
||||
print(' - port: ${nodeListItem.port}');
|
||||
print(' - uuid: ${nodeListItem.uuid}');
|
||||
print(' - method: ${nodeListItem.method}');
|
||||
print(' - config: ${nodeListItem.config}');
|
||||
print(' - protocols: ${nodeListItem.protocols}');
|
||||
|
||||
// 🔧 尝试从 config 字段解析 transport 配置
|
||||
Map<String, dynamic>? transportConfig;
|
||||
Map<String, dynamic>? securityConfig;
|
||||
|
||||
// 🔧 关键修复:优先从 protocols 字段解析配置
|
||||
if (nodeListItem.protocols.isNotEmpty) {
|
||||
try {
|
||||
final protocolsList = jsonDecode(nodeListItem.protocols) as List;
|
||||
print('📄 解析到 protocols 数组,共 ${protocolsList.length} 个协议');
|
||||
|
||||
// 查找匹配当前协议类型的配置
|
||||
Map<String, dynamic>? matchedProtocol;
|
||||
for (var protocol in protocolsList) {
|
||||
final protocolMap = protocol as Map<String, dynamic>;
|
||||
final type = protocolMap['type']?.toString().toLowerCase() ?? '';
|
||||
final enable = protocolMap['enable'] ?? true;
|
||||
|
||||
print(' 📋 协议: type=$type, enable=$enable');
|
||||
|
||||
// 匹配协议类型(注意 hysteria 和 hysteria2 都匹配 hysteria)
|
||||
if (type == nodeListItem.protocol.toLowerCase() ||
|
||||
(nodeListItem.protocol == 'hysteria' && type == 'hysteria2') ||
|
||||
(nodeListItem.protocol == 'hysteria2' && type == 'hysteria')) {
|
||||
matchedProtocol = protocolMap;
|
||||
print(' ✅ 找到匹配的协议配置: $type');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedProtocol != null) {
|
||||
// 提取 transport 配置
|
||||
if (matchedProtocol['network'] != null || matchedProtocol['transport'] != null) {
|
||||
final network = matchedProtocol['network'] ?? matchedProtocol['transport'];
|
||||
print(' 📡 传输协议: $network');
|
||||
|
||||
if (network == 'ws' || network == 'websocket') {
|
||||
transportConfig = {
|
||||
'type': 'ws',
|
||||
'path': matchedProtocol['ws_path'] ?? matchedProtocol['path'] ?? '/',
|
||||
};
|
||||
final host = matchedProtocol['ws_host'] ?? matchedProtocol['host'];
|
||||
if (host != null && host.toString().isNotEmpty) {
|
||||
transportConfig['headers'] = {'Host': host.toString()};
|
||||
}
|
||||
print(' ✅ WebSocket transport: $transportConfig');
|
||||
} else if (network == 'grpc') {
|
||||
transportConfig = {
|
||||
'type': 'grpc',
|
||||
'service_name': matchedProtocol['grpc_service_name'] ?? matchedProtocol['service_name'] ?? '',
|
||||
};
|
||||
print(' ✅ gRPC transport: $transportConfig');
|
||||
} else if (network == 'http' || network == 'h2') {
|
||||
transportConfig = {
|
||||
'type': 'http',
|
||||
'host': [matchedProtocol['http_host'] ?? matchedProtocol['host'] ?? ''],
|
||||
'path': matchedProtocol['http_path'] ?? matchedProtocol['path'] ?? '/',
|
||||
};
|
||||
print(' ✅ HTTP transport: $transportConfig');
|
||||
}
|
||||
}
|
||||
|
||||
// 提取 security 配置
|
||||
if (matchedProtocol['tls'] != null || matchedProtocol['security'] != null) {
|
||||
// 🔧 关键修复:读取 security 字段判断是否启用 TLS
|
||||
final security = matchedProtocol['security']?.toString().toLowerCase() ?? '';
|
||||
final tlsEnabled = security == 'tls' || security == 'reality';
|
||||
|
||||
securityConfig = {
|
||||
'tls_enabled': tlsEnabled, // ← 新增:记录 TLS 是否启用
|
||||
'sni': matchedProtocol['sni'] ?? matchedProtocol['server_name'],
|
||||
'allow_insecure': matchedProtocol['allow_insecure'] ?? matchedProtocol['insecure'] ?? true,
|
||||
'fingerprint': matchedProtocol['fingerprint'] ?? 'chrome',
|
||||
};
|
||||
print(' ✅ Security config: security=$security, tls_enabled=$tlsEnabled, config=$securityConfig');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('⚠️ 解析 protocols 字段失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// 兜底:尝试从 config 字段解析(旧API格式)
|
||||
if (transportConfig == null && nodeListItem.config.isNotEmpty) {
|
||||
try {
|
||||
final configJson = jsonDecode(nodeListItem.config) as Map<String, dynamic>;
|
||||
print('📄 解析到 config JSON: $configJson');
|
||||
|
||||
// 提取 transport 配置
|
||||
if (configJson['transport'] != null && configJson['transport'] != 'tcp') {
|
||||
transportConfig = _buildTransport(configJson);
|
||||
print('✅ 从 config 找到 transport 配置: $transportConfig');
|
||||
}
|
||||
|
||||
// 提取 security_config
|
||||
if (configJson['security_config'] != null) {
|
||||
securityConfig = configJson['security_config'] as Map<String, dynamic>;
|
||||
print('✅ 从 config 找到 security_config: $securityConfig');
|
||||
}
|
||||
} catch (e) {
|
||||
print('⚠️ 解析 config 字段失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
switch (nodeListItem.protocol) {
|
||||
case "shadowsocks":
|
||||
@ -268,19 +373,30 @@ class KROutboundItem {
|
||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||||
.hasMatch(nodeListItem.serverAddr);
|
||||
|
||||
// 🔧 优先使用 security_config 中的 SNI
|
||||
String serverName = nodeListItem.serverAddr;
|
||||
if (securityConfig != null && securityConfig['sni'] != null && securityConfig['sni'].toString().isNotEmpty) {
|
||||
serverName = securityConfig['sni'].toString();
|
||||
}
|
||||
|
||||
// 🔧 关键修复:根据 security_config 判断是否启用 TLS
|
||||
final bool vlessTlsEnabled = securityConfig?['tls_enabled'] ?? false;
|
||||
print('🔐 VLESS TLS 状态: enabled=$vlessTlsEnabled');
|
||||
|
||||
config = {
|
||||
"type": "vless",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": nodeListItem.port,
|
||||
"uuid": nodeListItem.uuid,
|
||||
"tls": {
|
||||
if (transportConfig != null) "transport": transportConfig,
|
||||
if (vlessTlsEnabled) "tls": {
|
||||
"enabled": true,
|
||||
if (isDomain) "server_name": nodeListItem.serverAddr,
|
||||
"insecure": true,
|
||||
if (isDomain) "server_name": serverName,
|
||||
"insecure": securityConfig?['allow_insecure'] ?? true,
|
||||
"utls": {
|
||||
"enabled": true,
|
||||
"fingerprint": "chrome"
|
||||
"fingerprint": securityConfig?['fingerprint'] ?? "chrome"
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -292,6 +408,16 @@ class KROutboundItem {
|
||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||||
.hasMatch(nodeListItem.serverAddr);
|
||||
|
||||
// 🔧 优先使用 security_config 中的 SNI
|
||||
String serverName = nodeListItem.serverAddr;
|
||||
if (securityConfig != null && securityConfig['sni'] != null && securityConfig['sni'].toString().isNotEmpty) {
|
||||
serverName = securityConfig['sni'].toString();
|
||||
}
|
||||
|
||||
// 🔧 关键修复:根据 security_config 判断是否启用 TLS
|
||||
final bool tlsEnabled = securityConfig?['tls_enabled'] ?? false;
|
||||
print('🔐 TLS 状态: enabled=$tlsEnabled');
|
||||
|
||||
config = {
|
||||
"type": "vmess",
|
||||
"tag": nodeListItem.name,
|
||||
@ -300,11 +426,15 @@ class KROutboundItem {
|
||||
"uuid": nodeListItem.uuid,
|
||||
"alter_id": 0,
|
||||
"security": "auto",
|
||||
"tls": {
|
||||
if (transportConfig != null) "transport": transportConfig,
|
||||
if (tlsEnabled) "tls": {
|
||||
"enabled": true,
|
||||
if (isDomain) "server_name": nodeListItem.serverAddr,
|
||||
"insecure": true,
|
||||
"utls": {"enabled": true, "fingerprint": "chrome"}
|
||||
if (isDomain) "server_name": serverName,
|
||||
"insecure": securityConfig?['allow_insecure'] ?? true,
|
||||
"utls": {
|
||||
"enabled": true,
|
||||
"fingerprint": securityConfig?['fingerprint'] ?? "chrome"
|
||||
}
|
||||
}
|
||||
};
|
||||
print('✅ VMess 节点配置构建成功: ${nodeListItem.name}');
|
||||
@ -315,17 +445,27 @@ class KROutboundItem {
|
||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||||
.hasMatch(nodeListItem.serverAddr);
|
||||
|
||||
// 🔧 优先使用 security_config 中的 SNI
|
||||
String serverName = nodeListItem.serverAddr;
|
||||
if (securityConfig != null && securityConfig['sni'] != null && securityConfig['sni'].toString().isNotEmpty) {
|
||||
serverName = securityConfig['sni'].toString();
|
||||
}
|
||||
|
||||
config = {
|
||||
"type": "trojan",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": nodeListItem.port,
|
||||
"password": nodeListItem.uuid,
|
||||
if (transportConfig != null) "transport": transportConfig,
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
if (isDomain) "server_name": nodeListItem.serverAddr,
|
||||
"insecure": true,
|
||||
"utls": {"enabled": true, "fingerprint": "chrome"}
|
||||
if (isDomain) "server_name": serverName,
|
||||
"insecure": securityConfig?['allow_insecure'] ?? true,
|
||||
"utls": {
|
||||
"enabled": true,
|
||||
"fingerprint": securityConfig?['fingerprint'] ?? "chrome"
|
||||
}
|
||||
}
|
||||
};
|
||||
print('✅ Trojan 节点配置构建成功: ${nodeListItem.name}');
|
||||
|
||||
@ -525,6 +525,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
break;
|
||||
case SingboxStarted():
|
||||
KRLogUtil.kr_i('🟢 状态: 已启动', tag: 'HomeController');
|
||||
print('🔵 状态变为 Started, 当前延迟=${kr_currentNodeLatency.value}');
|
||||
|
||||
// 取消连接超时处理
|
||||
_cancelConnectionTimeout();
|
||||
kr_connectText.value = AppTranslations.kr_home.connected;
|
||||
@ -533,6 +535,14 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
kr_isLatency.value = false;
|
||||
kr_isConnected.value = true;
|
||||
|
||||
// 🔧 关键修复:如果延迟还是-1(连接中状态),立即设置为0(已连接但延迟未知)
|
||||
if (kr_currentNodeLatency.value == -1) {
|
||||
print('🔵 强制将延迟从 -1 更新为 0');
|
||||
kr_currentNodeLatency.value = 0;
|
||||
kr_currentNodeLatency.refresh(); // 强制刷新延迟值
|
||||
print('🔵 延迟值已刷新');
|
||||
}
|
||||
|
||||
// 🔧 修复:立即尝试更新延迟值
|
||||
_kr_updateLatencyOnConnected();
|
||||
|
||||
@ -540,6 +550,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
kr_isConnected.refresh();
|
||||
// 强制更新UI
|
||||
update();
|
||||
print('🔵 状态更新完成,当前延迟=${kr_currentNodeLatency.value}');
|
||||
break;
|
||||
case SingboxStopping():
|
||||
KRLogUtil.kr_i('🟠 状态: 正在停止', tag: 'HomeController');
|
||||
@ -1110,9 +1121,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
// 选择节点
|
||||
void kr_selectNode(String tag) {
|
||||
void kr_selectNode(String tag) async {
|
||||
try {
|
||||
kr_currentNodeLatency.value = -1;
|
||||
kr_cutTag.value = tag;
|
||||
kr_currentNodeName.value = tag;
|
||||
|
||||
@ -1124,7 +1134,18 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
// 🔧 修复:只有在核心已启动时才选择节点,避免触发重启
|
||||
if (KRSingBoxImp.instance.kr_status.value == SingboxStarted()) {
|
||||
KRSingBoxImp.instance.kr_selectOutbound(tag);
|
||||
print('🔵 节点已选择且VPN正在运行,切换到: $tag');
|
||||
// 🔧 关键修复:切换节点时设置为-1(切换中)
|
||||
kr_currentNodeLatency.value = -1;
|
||||
|
||||
// 🔧 关键修复:使用 await 等待节点切换完成
|
||||
try {
|
||||
await KRSingBoxImp.instance.kr_selectOutbound(tag);
|
||||
print('🔵 节点切换命令已执行完成: $tag');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 节点切换失败: $e', tag: 'HomeController');
|
||||
print('🔵 节点切换失败: $e');
|
||||
}
|
||||
|
||||
// 🔧 修复:选择节点后启动延迟值更新(带超时保护)
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
@ -1132,10 +1153,22 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
KRLogUtil.kr_w('⚠️ 选择节点后延迟值未更新,尝试手动更新', tag: 'HomeController');
|
||||
if (!_kr_tryUpdateDelayFromActiveGroups()) {
|
||||
kr_currentNodeLatency.value = 0;
|
||||
kr_currentNodeLatency.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 🔧 关键修复:核心未启动时,根据连接状态设置延迟
|
||||
print('🔵 节点已选择但VPN未运行: $tag, 当前状态=${KRSingBoxImp.instance.kr_status.value}');
|
||||
if (kr_isConnected.value) {
|
||||
// 如果显示已连接但实际未运行,重置状态
|
||||
kr_currentNodeLatency.value = -2;
|
||||
} else {
|
||||
// 正常未连接状态
|
||||
kr_currentNodeLatency.value = -2;
|
||||
}
|
||||
kr_currentNodeLatency.refresh();
|
||||
|
||||
// 🔧 修复:核心未启动时,仍需保存用户选择,以便启动VPN时应用
|
||||
KRLogUtil.kr_i('💾 核心未启动,保存节点选择以便稍后应用: $tag', tag: 'HomeController');
|
||||
KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: tag).then((_) {
|
||||
|
||||
@ -99,6 +99,7 @@ class KRHomeConnectionInfoView extends GetView<KRHomeController> {
|
||||
children: [
|
||||
Obx(() {
|
||||
final delay = controller.kr_currentNodeLatency.value;
|
||||
print('🔵 UI延迟显示更新: delay=$delay');
|
||||
|
||||
// 获取延迟颜色
|
||||
Color getLatencyColor(int delay) {
|
||||
|
||||
@ -1081,6 +1081,34 @@ class KRSingBoxImp {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 🔧 关键修复:恢复用户选择的节点
|
||||
try {
|
||||
final selectedNode = await KRSecureStorage().kr_readData(key: _keySelectedNode);
|
||||
if (selectedNode != null && selectedNode.isNotEmpty) {
|
||||
KRLogUtil.kr_i('🎯 恢复用户选择的节点: $selectedNode', tag: 'SingBox');
|
||||
print('🔵 启动后恢复节点选择: $selectedNode');
|
||||
|
||||
// 延迟500ms确保sing-box完全启动
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
// 🔧 关键修复:使用 await 等待节点切换完成
|
||||
try {
|
||||
await kr_selectOutbound(selectedNode);
|
||||
KRLogUtil.kr_i('✅ 节点已切换到用户选择: $selectedNode', tag: 'SingBox');
|
||||
print('🔵 节点切换成功: $selectedNode');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 节点切换失败: $e', tag: 'SingBox');
|
||||
print('🔵 节点切换失败: $e');
|
||||
}
|
||||
} else {
|
||||
KRLogUtil.kr_i('ℹ️ 没有保存的节点选择,使用默认配置', tag: 'SingBox');
|
||||
print('🔵 没有保存的节点选择,使用默认');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 恢复节点选择失败: $e', tag: 'SingBox');
|
||||
print('🔵 恢复节点选择失败: $e');
|
||||
}
|
||||
});
|
||||
} catch (e, stackTrace) {
|
||||
KRLogUtil.kr_e('💥 SingBox 启动异常: $e', tag: 'SingBox');
|
||||
@ -1267,29 +1295,30 @@ class KRSingBoxImp {
|
||||
// 节点选择监控定时器
|
||||
Timer? _nodeSelectionTimer;
|
||||
|
||||
void kr_selectOutbound(String tag) async {
|
||||
Future<void> kr_selectOutbound(String tag) async {
|
||||
KRLogUtil.kr_i('🎯 [v2.1] 开始选择出站节点: $tag', tag: 'SingBox');
|
||||
KRLogUtil.kr_i('📊 当前活动组数量: ${kr_activeGroups.length}', tag: 'SingBox');
|
||||
|
||||
// 持久化用户选择的节点(异步执行,不阻塞UI)
|
||||
KRSecureStorage().kr_saveData(key: _keySelectedNode, value: tag).then((_) {
|
||||
// 🔧 关键修复:使用 await 确保保存完成
|
||||
try {
|
||||
await KRSecureStorage().kr_saveData(key: _keySelectedNode, value: tag);
|
||||
KRLogUtil.kr_i('✅ 节点选择已保存: $tag', tag: 'SingBox');
|
||||
}).catchError((e) {
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 保存节点选择失败: $e', tag: 'SingBox');
|
||||
});
|
||||
}
|
||||
|
||||
// ⚠️ 关键修复:确保 command client 已初始化
|
||||
// 借鉴 Hiddify:通过触发 watchGroups() 来初始化 command client
|
||||
_kr_ensureCommandClientInitialized().then((_) {
|
||||
// 🔧 关键修复:使用 await 确保 command client 初始化完成
|
||||
try {
|
||||
await _kr_ensureCommandClientInitialized();
|
||||
KRLogUtil.kr_i('✅ Command client 已就绪,执行节点切换', tag: 'SingBox');
|
||||
|
||||
// 🔄 使用重试机制执行节点选择(异步执行,不阻塞UI)
|
||||
_kr_selectOutboundWithRetry("select", tag, maxAttempts: 3, initialDelay: 50).catchError((e) {
|
||||
KRLogUtil.kr_e('❌ 节点选择失败: $e', tag: 'SingBox');
|
||||
});
|
||||
}).catchError((e) {
|
||||
KRLogUtil.kr_e('❌ Command client 初始化失败,节点切换取消: $e', tag: 'SingBox');
|
||||
});
|
||||
// 🔧 关键修复:使用 await 等待节点选择完成
|
||||
await _kr_selectOutboundWithRetry("select", tag, maxAttempts: 3, initialDelay: 50);
|
||||
KRLogUtil.kr_i('✅ 节点切换完成: $tag', tag: 'SingBox');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 节点选择失败: $e', tag: 'SingBox');
|
||||
rethrow; // 抛出异常,让调用者知道失败了
|
||||
}
|
||||
|
||||
// 🔄 如果用户选择了具体节点(不是 auto),启动定期检查和重新选择
|
||||
// 这是为了防止 urltest 自动覆盖用户的手动选择
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user