hi-client/docs/CLASH_TROUBLESHOOTING.md
Rust d02eed3bd8 docs: 添加 GitHub Actions 构建文档并锁定 libcore 版本
- 添加完整的 GitHub Actions 构建指南文档
  - BUILD_GUIDE.md: Android 详细构建指南
  - MULTIPLATFORM_GUIDE.md: 多平台构建指南
  - HOW_TO_BUILD.md: 分步操作教程
  - QUICKSTART.md: 3步快速开始指南
  - INDEX.md: 文档总览索引
  - README.md: 基础说明
- 创建 docs/ 目录存放项目文档
- 锁定 libcore 子模块到 f993a57 (v3.1.7)
  - 防止在线编译时使用最新版本
  - 确保构建稳定性和一致性
2025-10-27 23:11:21 +08:00

8.4 KiB

Clash Meta 故障排查指南

常见运行时问题

问题 1: VPN 连接失败,日志显示 "PermissionMonitor error 22 (EINVAL)"

症状:

E/VPNService: PermissionMonitor error 22 (EINVAL)
E/Clash: TUN 设备启动失败

原因:

  • VPN Builder 配置的路由规则无效
  • 缺少 bypass-LAN 路由配置
  • 路由 CIDR 格式错误

解决方案:

  1. 检查 getAndroidVpnOptions() 返回的路由列表:
final options = await KRClashImp().getAndroidVpnOptions();
print('路由数量: ${options?['routeAddress']?.length}');
print('路由列表: ${options?['routeAddress']}');
  1. 确认配置包含完整的 bypass-LAN 路由:
# clash_config.yaml
tun:
  route-exclude-address:
    - 10.0.0.0/8
    - 100.64.0.0/10
    - 127.0.0.0/8
    - 169.254.0.0/16
    - 172.16.0.0/12
    - 192.0.0.0/24
    - 192.0.2.0/24
    - 192.88.99.0/24
    - 192.168.0.0/16
    - 198.18.0.0/15
    - 198.51.100.0/24
    - 203.0.113.0/24
    - 224.0.0.0/3
    - fc00::/7
    - fe80::/10
    - ff00::/8
  1. 验证 VPN Builder 配置:
// VPNService.kt
builder
  .addAddress("172.19.0.1/30")
  .addRoute("0.0.0.0/1")      // ✅ 拆分默认路由
  .addRoute("128.0.0.0/1")    // ✅ 避免 0.0.0.0/0
  .addRoute("10.0.0.0/8")     // ✅ bypass-LAN
  // ...

参考: lib/app/services/clash_imp/kr_clash_imp.dart:192


问题 2: FFI 初始化崩溃 "Go runtime already initialized"

症状:

F/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR)
E/Clash: Go runtime already initialized

原因:

  • 多线程并发调用 _ensureInitialized()
  • Go 运行时被重复初始化

解决方案:

已在 kr_clash_imp.dart 中修复,使用 Completer 实现并发安全:

Future<void> _ensureInitialized() async {
  if (_initialized) return;
  if (_initLock != null) {
    await _initLock!.future;  // ✅ 等待其他初始化完成
    return;
  }
  _initLock = Completer<void>();
  // ... 执行初始化
}

验证修复:

// 并发测试
await Future.wait([
  KRClashImp().start(...),
  KRClashImp().getAndroidVpnOptions(),
  KRClashImp().getTraffic(),
]);
// ✅ 应该不会崩溃

参考: lib/app/services/clash_imp/kr_clash_imp.dart:43


问题 3: 模拟器无法联网 "Network unreachable"

症状:

E/Clash: 网络不可达
W/VPNService: 底层网络丢失

原因:

  • 模拟器需要显式设置底层网络
  • 缺少 setUnderlyingNetworks() 调用

解决方案:

已在 VPNService.kt 中实现:

// Android P+ 需要设置底层网络
private val defaultNetworkCallback by lazy {
    object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            setUnderlyingNetworks(arrayOf(network))  // ✅ 关键!
        }
    }
}

// 注册回调
connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback)

验证修复:

# 在 MuMu 模拟器测试
adb logcat | grep "底层网络"
# 应该看到: "底层网络可用: NetworkIdentity{...}"

参考: android/.../bg/VPNService.kt:32-66


问题 4: 配置文件解析失败 "invalid YAML"

症状:

E/Clash: 启动失败: yaml: unmarshal errors:
  line 10: cannot unmarshal !!str `8388` into int

原因:

  • YAML 类型不匹配 (字符串 vs 整数)
  • 配置格式错误

解决方案:

检查 ClashConfigGenerator.generate():

proxies:
  - name: "Server-1"
    type: ss
    server: "example.com"
    port: 8388              #  整数,不要引号
    password: "password"    #  字符串,使用引号
    cipher: "aes-256-gcm"

调试方法:

// 打印生成的配置
final config = ClashConfigGenerator.generate(...);
print('配置预览:\n$config');

// 保存到文件手动检查
File('/tmp/clash_debug.yaml').writeAsStringSync(config);

参考: lib/app/services/clash_imp/clash_config_generator.dart


问题 5: "quickStart timeout" 启动超时

症状:

W/Clash: 启动超时 (10秒)
E/Clash: quickStart 未收到回调

原因:

  • Go 核心启动耗时过长
  • ReceivePort 未正确监听
  • 回调被阻塞

解决方案:

  1. 增加超时时间:
_isRunning = await completer.future.timeout(
  const Duration(seconds: 20),  // ✅ 增加到 20 秒
  onTimeout: () {
    print('⚠️ 启动超时');
    return false;
  },
);
  1. 检查 Go 核心日志:
// ClashService.kt - 添加日志回调
tempMethodChannel.setMethodCallHandler { call, result ->
    if (call.method == "log") {
        Log.d(TAG, "Go 核心日志: ${call.arguments}")
    }
}
  1. 使用 Service Isolate 避免阻塞:
// ClashService.kt:102 - 已实现
serviceEngine?.dartExecutor?.executeDartEntrypoint(entrypoint)

参考: lib/app/services/clash_imp/kr_clash_imp.dart:145


问题 6: 流量统计不更新

症状:

I/Clash: 流量统计: {"upload": 0, "download": 0}  # 始终为 0

原因:

  • TUN 设备未正确启动
  • 流量未通过代理

解决方案:

  1. 确认 TUN 启动成功:
final tunStarted = await KRClashImp().startTun(fd, protectCallback);
print('TUN 启动状态: $tunStarted');  // 应该为 true
  1. 检查路由配置:
# 在设备上检查路由表
adb shell "ip route show table all | grep tun"
  1. 测试代理连接:
# 使用 curl 测试
adb shell "curl -v http://www.google.com"
# 应该看到代理日志

参考: lib/app/services/clash_imp/kr_clash_imp.dart:261


日志分析

启用详细日志

// lib/app/services/clash_imp/kr_clash_imp.dart
// 添加更详细的日志
print('📨 [Clash] 收到消息: ${jsonEncode(message)}');
print('📋 [Clash] 配置完整内容:\n$config');

Android Logcat 过滤

# 只看 Clash 相关日志
adb logcat -s "A/Clash" "E/Clash" "W/Clash"

# 实时监控 FFI 调用
adb logcat | grep -E "(ClashFFI|quickStart|getAndroidVpnOptions)"

# 保存日志到文件
adb logcat -d > clash_debug.log

Dart Observatory 调试

# 启用 Dart Observatory
flutter run --observatory-port=8181

# 在浏览器打开
open http://localhost:8181

# 查看 Isolate 状态
# - Main Isolate (UI)
# - Service Isolate (Clash 后台)

性能问题

内存泄漏

症状:

  • 应用内存持续增长
  • 最终 OOM 崩溃

排查:

// 确保调用 dispose()
@override
void dispose() {
  KRClashImp().dispose();
  super.dispose();
}

// 检查 ReceivePort 是否关闭
receivePort.listen((message) {
  // ...
  receivePort.close();  // ✅ 必须关闭!
});

CPU 占用过高

症状:

  • 设备发热
  • 电池消耗快

排查:

# 查看 CPU 占用
adb shell "top -m 10"

# 使用 Profiler
flutter run --profile
# DevTools → CPU Profiler

优化:

// 降低流量统计频率
Timer.periodic(const Duration(seconds: 2), (timer) {  // ✅ 2秒而非1秒
  final traffic = await KRClashImp().getTraffic();
  // ...
});

崩溃分析

获取崩溃堆栈

# Native 崩溃
adb logcat -d | grep "backtrace"

# 使用 ndk-stack 解析
adb logcat | ndk-stack -sym android/app/build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a

常见崩溃模式

SIGSEGV (段错误)

原因: FFI 指针使用错误

示例:

// ❌ 错误: 释放后使用
final ptr = 'hello'.toNativeUtf8();
malloc.free(ptr);
_ffi!.someFunction(ptr);  // 崩溃!

// ✅ 正确: 使用后释放
final ptr = 'hello'.toNativeUtf8();
_ffi!.someFunction(ptr);
malloc.free(ptr);

SIGABRT (异常终止)

原因: Go panic 未恢复

解决: 在 Go 侧添加 recover:

//export quickStart
func quickStart(...) {
    defer func() {
        if r := recover(); r != nil {
            log.Println("Panic recovered:", r)
        }
    }()
    // ...
}

获取帮助

收集诊断信息

创建 Bug 报告时请包含:

  1. 系统信息:
adb shell "getprop ro.build.version.release"  # Android 版本
adb shell "getprop ro.product.cpu.abi"        # CPU 架构
  1. 应用日志:
adb logcat -d > full_log.txt
  1. 配置文件:
// 脱敏后的 clash_config.yaml
print(config.replaceAll(RegExp(r'password:.*'), 'password: ***'));
  1. 复现步骤:
  • 详细操作流程
  • 预期结果 vs 实际结果
  • 复现概率

相关资源