clash_service.dart 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. import 'dart:io';
  2. import 'dart:async';
  3. import 'package:get/get.dart';
  4. import 'package:yaml_edit/yaml_edit.dart';
  5. import 'package:path/path.dart' as path;
  6. import '../../common/constants.dart';
  7. import '../../const/const.dart';
  8. import '../../controller/controllers.dart';
  9. import '../../data/model/NodeMode.dart';
  10. import '../../utils/shell.dart';
  11. import '../../utils/utils.dart';
  12. import '../mode/clash_config.dart';
  13. class ClashService extends GetxController {
  14. Process? clashCoreProcess;
  15. final coreStatus = RunningState.stoped.obs;
  16. final serviceMode = false.obs;
  17. bool get clashServiceIsRuning => coreStatus.value == RunningState.running;
  18. Future<bool> startClashCore() async {
  19. final timeout = const Duration(seconds: 30);
  20. final checkInterval = const Duration(milliseconds: 200);
  21. var startTime = DateTime.now();
  22. await controllers.config.readClashCoreApi();
  23. try {
  24. controllers.global.updateMsg("启动内核---${controllers.config.config.value.selected}");
  25. if (controllers.config.config.value.selected == 'init_proxy.yaml') {
  26. controllers.global.updateMsg("启动内核初始化");
  27. } else {
  28. controllers.global.updateMsg("启动内核");
  29. }
  30. coreStatus.value = RunningState.starting;
  31. int? exitCode;
  32. clashCoreProcess = await Process.start(
  33. Files.assetsCCore.path,
  34. ['-d', Paths.config.path, '-f', path.join(Paths.config.path, controllers.config.config.value.selected)],
  35. mode: ProcessStartMode.inheritStdio
  36. );
  37. clashCoreProcess!.exitCode.then((code) => exitCode = code);
  38. if (exitCode != null && exitCode != 0) {
  39. controllers.global.updateMsg("启动内核错误,请重启点电脑测试");
  40. return false;
  41. }
  42. controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value);
  43. while (DateTime.now().difference(startTime) < timeout) {
  44. try {
  45. controllers.global.updateMsg("等待内核启动..");
  46. await controllers.core.fetchHello();
  47. break;
  48. } catch (_) {
  49. await Future.delayed(checkInterval);
  50. }
  51. }
  52. if (DateTime.now().difference(startTime) >= timeout) {
  53. coreStatus.value = RunningState.error;
  54. controllers.global.updateMsg("内核启动超时,重新点击加速后尝试。");
  55. return false;
  56. }
  57. await controllers.core.updateConfig();
  58. coreStatus.value = RunningState.running;
  59. controllers.global.updateMsg("内核状态:${coreStatus.value == RunningState.running} ");
  60. return true;
  61. } catch (e) {
  62. controllers.global.updateMsg("启动内核错误");
  63. coreStatus.value = RunningState.error;
  64. return false;
  65. }
  66. }
  67. Future<void> stopClashCore() async {
  68. coreStatus.value = RunningState.stopping;
  69. await controllers.global.closeProxy();
  70. clashCoreProcess?.kill();
  71. killProcess(ClashName.name);
  72. coreStatus.value = RunningState.stoped;
  73. }
  74. Future<bool> initClashCoreConfig() async {
  75. controllers.config.config.value.selected = Files.makeInitProxyConfig.path;
  76. await startClashCore();
  77. if (coreStatus.value == RunningState.error) {
  78. controllers.global.updateMsg("启动内核失败...");
  79. return false;
  80. } else {
  81. await controllers.core.updateVersion();
  82. controllers.global.updateMsg("启动内核成功...");
  83. return true;
  84. }
  85. }
  86. Future<void> stopClash() async {
  87. controllers.config.config.value.selected = Files.makeInitProxyConfig.path;
  88. if (coreStatus.value == RunningState.running) {
  89. await controllers.config.readClashCoreApi();
  90. controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value);
  91. await controllers.core.changeConfig(path.join(Paths.config.path, controllers.config.config.value.selected));
  92. }
  93. }
  94. Future<void> reloadClashCore() async {
  95. try {
  96. controllers.config.config.value.selected = Files.makeProxyConfig.path;
  97. if (coreStatus.value == RunningState.running) {
  98. controllers.global.updateMsg("切换配置...");
  99. await controllers.config.readClashCoreApi();
  100. controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value);
  101. await controllers.core.changeConfig(path.join(Paths.config.path, controllers.config.config.value.selected));
  102. controllers.global.updateMsg("fetchReloadConfig${controllers.config.clashCoreApiAddress.value}...");
  103. }
  104. } catch (e) {
  105. controllers.global.updateMsg("重新配置...");
  106. await controllers.config.readClashCoreApi();
  107. controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value);
  108. await controllers.core.changeConfig(path.join(Paths.config.path, controllers.config.config.value.selected));
  109. }
  110. }
  111. Future<String> generateYamlConfig(List<NodeMode> nodes) async {
  112. final config = ClashConfig(
  113. mixedPort: 9788,
  114. allowLan: true,
  115. bindAddress: '*',
  116. mode: controllers.global.modesSelect.value,
  117. logLevel: 'info',
  118. externalController: '127.0.0.1:9799',
  119. unifiedDelay: false,
  120. geodataMode: true,
  121. tcpConcurrent: false,
  122. findProcessMode: 'strict',
  123. globalClientFingerprint: 'chrome',
  124. profile: {
  125. 'store-selected': true,
  126. 'store-fake-ip': true,
  127. },
  128. sniffer: Sniffer(
  129. enable: true,
  130. sniff: {
  131. 'HTTP': {
  132. 'ports': [80, '8080-8880'],
  133. 'override-destination': true,
  134. },
  135. 'TLS': {
  136. 'ports': [443, 8443],
  137. },
  138. 'QUIC': {
  139. 'ports': [443, 8443],
  140. },
  141. },
  142. skipDomain: ['www.baidu.com'],
  143. ),
  144. dns: DNS(
  145. enable: true,
  146. listen: kDnsListenPort,
  147. ipv6: false,
  148. enhancedMode: kRedirHostMode,
  149. fakeIpFilter: null,
  150. nameserver: kDomesticDNS,
  151. proxyServerNameserver: kDomesticDNS,
  152. nameserverPolicy: {
  153. 'geosite:cn,private': kDomesticDNS,
  154. 'geosite:geolocation-!cn': kForeignDNS,
  155. },
  156. ),
  157. tun: Tun(
  158. enable: controllers.global.routeModesSelect.value == "tun" ? true:false,
  159. stack: 'system',
  160. autoRoute: true,
  161. autoRedirect: true,
  162. autoDetectInterface: true,
  163. dnsHijack: ['any:53'],
  164. ),
  165. proxies: [],
  166. proxyGroups: [
  167. ProxyGroup(
  168. name: 'proxy',
  169. type: 'select',
  170. proxies: [],
  171. ),
  172. ],
  173. rules: [
  174. 'GEOSITE,geolocation-!cn,proxy',
  175. 'GEOSITE,cn,DIRECT',
  176. 'GEOIP,CN,DIRECT',
  177. 'MATCH,proxy',
  178. ],
  179. );
  180. for (final node in nodes) {
  181. BaseProxy proxy;
  182. switch (node.type) {
  183. case 'trojan':
  184. proxy = TrojanProxy(
  185. name: node.name ?? '',
  186. server: node.host ?? '',
  187. port: node.port ?? 0,
  188. password: node.passwd ?? '',
  189. udp: node.udp == 1,
  190. sni: node.sni,
  191. );
  192. break;
  193. case 'shadowsocks':
  194. proxy = SSProxy(
  195. name: node.name ?? '',
  196. type: 'ss',
  197. server: node.host ?? '',
  198. port: node.port ?? 0,
  199. password: node.passwd ?? '',
  200. cipher: node.method ?? '',
  201. udp: node.udp == 1,
  202. );
  203. break;
  204. case 'v2ray':
  205. final type = (node.vless == 1) ? 'vless' : 'vmess';
  206. if (type == 'vless') {
  207. proxy = VlessProxy(
  208. name: node.name ?? '',
  209. uuid: node.uuid ?? '',
  210. server: node.host ?? '',
  211. port: node.port ?? 0,
  212. udp: node.udp == 1,
  213. flow: 'xtls-rprx-vision',
  214. tls: true,
  215. servername: node.v2Sni,
  216. realityOpts: {'public-key': node.vlessPulkey},
  217. network: 'tcp',
  218. );
  219. } else {
  220. proxy = VmessProxy(
  221. name: node.name ?? '',
  222. uuid: node.uuid ?? '',
  223. server: node.host ?? '',
  224. port: node.port ?? 0,
  225. alterId: node.v2AlterId,
  226. cipher: node.method,
  227. udp: node.udp == 1,
  228. );
  229. }
  230. break;
  231. default:
  232. continue;
  233. }
  234. config.proxies?.add(proxy.toJson());
  235. config.proxyGroups?[0].proxies.add(node.name ?? '');
  236. }
  237. return config.toYaml();
  238. }
  239. Future<void> saveConfigToFile(String filePath, List<NodeMode> nodes) async {
  240. try {
  241. final configYaml = await generateYamlConfig(nodes);
  242. final file = File(filePath);
  243. await file.writeAsString(configYaml);
  244. print('配置文件已成功保存到: $filePath');
  245. } catch (e) {
  246. print('保存配置文件时发生错误: $e');
  247. throw Exception('无法保存配置文件');
  248. }
  249. }
  250. }
  251. extension ConfigToYaml on ClashConfig {
  252. String toYaml() {
  253. final yamlMap = toJson();
  254. final yamlEditor = YamlEditor('');
  255. yamlEditor.update([], yamlMap);
  256. return yamlEditor.toString();
  257. }
  258. }