123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- import 'dart:io';
- import 'dart:async';
- import 'dart:convert';
- import 'package:dio/dio.dart';
- import 'package:get/get.dart';
- import 'package:naiyouwl/app/bean/ClashServiceInfo.dart';
- import 'package:naiyouwl/app/common/LogHelper.dart';
- import 'package:naiyouwl/app/const/const.dart';
- import 'package:naiyouwl/app/controller/controllers.dart';
- import 'package:naiyouwl/app/utils/logger.dart';
- import 'package:naiyouwl/app/utils/shell.dart';
- import 'package:naiyouwl/app/utils/system_dns.dart';
- import 'package:naiyouwl/app/utils/system_proxy.dart';
- import 'package:naiyouwl/app/utils/utils.dart';
- import 'package:path/path.dart' as path;
- import 'package:flutter/foundation.dart';
- import 'package:bot_toast/bot_toast.dart';
- import 'package:web_socket_channel/io.dart';
- import 'package:http/http.dart' as http;
- final headers = {"User-Agent": "ccore-for-flutter/0.0.1"};
- class ServiceController extends GetxController {
-
- var ip = "127.0.0.1";
- var url = "";
- var client = http.Client();
- var serviceMode = false.obs;
- var coreStatus = RunningState.stoped.obs;
- var serviceStatus = RunningState.stoped.obs;
- Process? clashServiceProcess;
- Process? clashCoreProcess;
- bool get isRunning => serviceStatus.value == RunningState.running && coreStatus.value == RunningState.running;
- bool get isCanOperationService =>
- ![RunningState.starting, RunningState.stopping].contains(serviceStatus.value) &&
- ![RunningState.starting, RunningState.stopping].contains(coreStatus.value);
- bool get isCanOperationCore =>
- serviceStatus.value == RunningState.running && ![RunningState.starting, RunningState.stopping].contains(coreStatus.value);
- bool get coreIsRuning => coreStatus.value == RunningState.running;
- bool get serviceIsRuning => serviceStatus.value == RunningState.running;
- ServiceController(
- );
- Future<void> initConfig() async{
- url = "$ip:${controllers.config.config.value.servicePort}";
- controllers.config.setSerivcePort(controllers.config.config.value.servicePort);
- }
- Future<void> startTunService() async {
- try {
- while (true) {
- final data = await fetchInfo() ?? ClashServiceInfo.fromJson({'code': -1 , 'mode' : '' , 'status': '-1','version' : '-1'});
- if( data.mode != 'service-mode')
- {
- if (serviceStatus.value == RunningState.running) {
- await stopService();
- }
- await install();
- } else {
- break;
- }
- }
- } catch (e) {
- serviceStatus.value = RunningState.error;
- log.debug(e.toString());
-
- }
- }
- Future<void> isService() async {
- try {
- final data = await fetchInfo() ?? ClashServiceInfo.fromJson({'code': -1 , 'mode' : '' , 'status': '-1','version' : '-1'});
- if(data.mode == 'service-mode'){
- serviceMode.value = true;
- controllers.global.updateMsg("服务模式");
- } else {
- serviceMode.value = false;
- controllers.global.updateMsg("用户模式");
- }
- } catch (_) {
- }
- }
- Future<void> startService() async {
- serviceStatus.value = RunningState.starting;
- try {
- final data = await fetchInfo();
- if(data == null){
- await startUserModeService();
- controllers.global.updateMsg("开启用户服务");
- return;
- }
- serviceMode.value = data.mode == 'service-mode';
- controllers.global.updateMsg("服务模式");
- } catch (e) {
- await startUserModeService();
- controllers.global.updateMsg("开启用户服务");
- if (serviceStatus.value == RunningState.error) return;
- }
- serviceStatus.value = RunningState.running;
- }
- Future<void> fixBinaryExecutePermissions(File file) async {
- final stat = await file.stat();
-
- final has = (stat.mode & 64) == 64;
- if (has) return;
- await Process.run('chmod', ['+x', file.path]);
- }
- Future<void> startUserModeService() async {
- serviceStatus.value = RunningState.stopping;
- serviceMode.value = false;
- final timeout = const Duration(seconds: 30);
- final checkInterval = const Duration(milliseconds: 200);
- var startTime = DateTime.now();
- try {
- final isRun = await controllers.global.onIsProcessRunning(Files.assetsClashService.path);
- if(isRun == true){
- await isService();
- serviceStatus.value = RunningState.running;
- return;
- }
- int? exitCode;
- clashServiceProcess = await Process.start(Files.assetsClashService.path, ['-port','${controllers.config.config.value.servicePort}','user-mode'], mode: ProcessStartMode.inheritStdio);
- clashServiceProcess!.exitCode.then((code) => exitCode = code);
- while (DateTime.now().difference(startTime) < timeout) {
- try {
- controllers.global.updateMsg("等待内核启动..");
- final ret = await fetchInfo();
- if(ret != null){
- if(ret.code == 0){
- break;
- }
- }
- break;
- } catch (_) {
-
- await Future.delayed(checkInterval);
- }
- }
-
- if (DateTime.now().difference(startTime) >= timeout) {
-
- coreStatus.value = RunningState.error;
- controllers.global.updateMsg("内核启动超时,重新点击加速后尝试。");
- return;
- }
- await fetchStartInit();
- serviceStatus.value = RunningState.running;
- } catch (e) {
- serviceStatus.value = RunningState.error;
-
- print(e.toString());
- }
- }
- Future<void> stopService() async {
- serviceStatus.value = RunningState.stopping;
- if (coreStatus.value == RunningState.running) await fetchStop();
- if (!serviceMode.value) {
- if (clashServiceProcess != null) {
- clashServiceProcess!.kill();
- clashServiceProcess = null;
- } else if (kDebugMode) {
- await killProcess(path.basename(Files.assetsClashService.path));
- }
- }
- serviceStatus.value = RunningState.stoped;
- }
-
- Future<void> waitServiceStart() async {
- while (true) {
- await Future.delayed(const Duration(milliseconds: 100));
- try {
- await client.post(Uri.http(url,'info'),headers: headers);
- break;
- } catch (_) {}
- }
- }
-
- Future<void> waitServiceStop() async {
- while (true) {
- await Future.delayed(const Duration(milliseconds: 100));
- try {
- await client.post(Uri.http(url,'info'),headers: headers);
- } catch (e) {
- break;
- }
- }
- }
- Future<ClashServiceInfo?> fetchInfo() async {
- try {
- final res = await client.post(Uri.http(url,'info'),headers: headers);
- var jsonResponse =
- jsonDecode(res.body) as Map<String, dynamic>;
- return ClashServiceInfo.fromJson(jsonResponse);
- } on http.ClientException catch (e) {
-
- print('Client Exception: ${e.message}');
- return null;
- } on Exception catch (e) {
-
- print('Exception: $e');
- return null;
- }
- return null;
- }
- IOWebSocketChannel fetchLogWs() {
- return IOWebSocketChannel.connect(Uri.parse('ws://127.0.0.1:${controllers.config.config.value.servicePort}/logs'), headers: headers);
- }
- Future<void> fetchStartInit() async {
- controllers.config.config.value.selected = 'init_proxy.yaml';
- controllers.global.updateMsg("启动内核---${controllers.config.config.value.selected}");
- if( controllers.config.config.value.selected == 'init_proxy.yaml'){
- controllers.global.updateMsg("启动内核初始化");
- } else {
- controllers.global.updateMsg("启动内核");
- }
- await fetchStop();
- try{
- var ut = Uri.http(url,'start');
- final body = json.encode({
- "args": [
- '-d',
- Paths.config.path,
- '-f',
- path.join(Paths.config.path, controllers.config.config.value.selected)
- ]
- });
- final res = await client.post(ut,body: body,headers: headers);
- var jsonResponse =
- jsonDecode(res.body) as Map<String, dynamic>;
- if (jsonResponse["code"] != 0) {
- coreStatus.value = RunningState.error;
- throw jsonResponse["msg"];
- }
- coreStatus.value = RunningState.running;
- } on http.ClientException catch (e) {
-
- print('Client Exception: ${e.message}');
- } on Exception catch (e) {
-
- print('Exception: $e');
- }
- }
- Future<void> fetchStart() async {
- controllers.config.config.value.selected = 'proxy.yaml';
- controllers.global.updateMsg("启动内核---${controllers.config.config.value.selected}");
- if( controllers.config.config.value.selected == 'init_proxy.yaml'){
- controllers.global.updateMsg("启动内核初始化");
- } else {
- controllers.global.updateMsg("启动内核");
- }
- await fetchStop();
- try{
- var ut = Uri.http(url,'start');
- final res = await client.post(ut,body: {"args": ['-d', Paths.config.path, '-f', path.join(Paths.config.path, controllers.config.config.value.selected)]},headers: headers);
- var jsonResponse =
- jsonDecode(res.body) as Map<String, dynamic>;
- if (jsonResponse["code"] != 0) throw jsonResponse["msg"];
- } on http.ClientException catch (e) {
-
- print('Client Exception: ${e.message}');
- } on Exception catch (e) {
-
- print('Exception: $e');
- }
- }
- Future<void> fetchSetProxy() async {
- try {
- var dns_ip = "";
- if(controllers.global.routeModesSelect.value == "tun"){
- dns_ip = "198.18.0.2";
- }
- var ut = Uri.http(url,'on');
- final body = json.encode({
- "http_port" : controllers.config.mixedPort.value,
- "https_port" : controllers.config.mixedPort.value,
- "socks_port" :controllers.config.mixedPort.value,
- "bypass": "127.0.0.1, 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 100.64.0.0/10, 17.0.0.0/8, localhost, *.local, *.crashlytics.com, seed-sequoia.siri.apple.com, sequoia.apple.com,xd.apple.com",
- "dns_ip": dns_ip,
- "dns_type":"",
- "address":"127.0.0.1"
- });
- await client.post(ut,body: body,headers: headers);
- } on http.ClientException catch (e) {
-
- print('Client Exception: ${e.message}');
- } on Exception catch (e) {
-
- print('Exception: $e');
- }
- }
- Future<void> fetchSetDnsProxy() async {
- try {
- var dns_ip = "";
- if(controllers.global.routeModesSelect.value == "tun"){
- dns_ip = "198.18.0.2";
- }
- var ut = Uri.http(url,'on');
- final body = json.encode({
- "http_port" : 0,
- "https_port" : 0,
- "socks_port" : 0,
- "bypass": "",
- "dns_ip": dns_ip,
- "dns_type":"",
- "address":"127.0.0.1"
- });
- await client.post(ut,body: body,headers: headers);
- } on http.ClientException catch (e) {
-
- print('Client Exception: ${e.message}');
- } on Exception catch (e) {
-
- print('Exception: $e');
- }
- }
- Future<void> fetchSetProxyStop() async {
- try {
- var dnsIp = "";
- if(controllers.global.routeModesSelect.value == "tun"){
- dnsIp = "223.5.5.5";
- }
- var ut = Uri.http(url,'off');
- final body = json.encode({
- "bypass": "",
- "dns_ip": dnsIp
- });
- await client.post(ut,body:body,headers: headers);
- } on http.ClientException catch (e) {
-
- print('Client Exception: ${e.message}');
- } on Exception catch (e) {
-
- print('Exception: $e');
- }
- }
- Future<void> fetchStop() async {
- try {
- var ut = Uri.http(url,'stop');
- await client.post(ut,headers:headers);
- } on http.ClientException catch (e) {
-
- print('Client Exception: ${e.message}');
- } on Exception catch (e) {
-
- print('Exception: $e');
- }
- }
- Future<void> install() async {
- await initConfig();
- final res = await runAsAdmin(Files.assetsClashService.path, ["-port","${controllers.config.config.value.servicePort}","stop", "uninstall", "install", "start"]);
- log.debug('install', res.stdout, res.stderr);
- if (res.exitCode != 0) throw res.stderr;
- await waitServiceStart();
- }
- Future<void> uninstall() async {
- final res = await runAsAdmin(Files.assetsClashService.path, ["stop", "uninstall"]);
- log.debug('uninstall', res.stdout, res.stderr);
- if (res.exitCode != 0) throw res.stderr;
- await waitServiceStop();
- }
- Future<void> serviceModeSwitch(bool open) async {
- if (serviceStatus.value == RunningState.running) await stopService();
- if (coreStatus.value == RunningState.running) await stopClashCore();
- try {
- controllers.global.updateMsg(open ? "安装服务" : "卸载服务");
- open ? await install() : await uninstall();
- } catch (e) {
- BotToast.showText(text: e.toString());
- }
- await startService();
- await fetchStartInit();
- }
- Future<bool> isPortAvailable(int port) async {
- try {
-
- var server = await ServerSocket.bind(InternetAddress.anyIPv4, port);
-
- await server.close();
-
- return true;
- } on SocketException {
-
- return false;
- }
- }
- Future<bool> startClashCore() async {
- final timeout = const Duration(seconds: 30);
- final checkInterval = const Duration(milliseconds: 200);
- var startTime = DateTime.now();
- await controllers.config.readClashCoreApi();
- try {
- controllers.global.updateMsg("启动内核---${controllers.config.config.value.selected}");
- if( controllers.config.config.value.selected == 'init_proxy.yaml'){
- controllers.global.updateMsg("启动内核初始化");
- } else {
- controllers.global.updateMsg("启动内核");
- }
- coreStatus.value = RunningState.starting;
- if(serviceMode.value == true){
- await fetchStart();
- }
- else
- {
- int? exitCode;
- clashCoreProcess = await Process.start(Files.assetsCCore.path, ['-d', Paths.config.path, '-f', path.join(Paths.config.path, controllers.config.config.value.selected)], mode: ProcessStartMode.inheritStdio);
- clashCoreProcess!.exitCode.then((code) => exitCode = code);
- if (exitCode != null && exitCode != 0) {
-
- controllers.global.updateMsg("启动内核错误,请重启点电脑测试");
- return false;
- }
- }
-
- log.debug("api${controllers.config.clashCoreApiAddress.value}");
- controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value);
- while (DateTime.now().difference(startTime) < timeout) {
- try {
- controllers.global.updateMsg("等待内核启动..");
- final ret = await controllers.core.fetchHello();
-
- break;
- } catch (_) {
-
- await Future.delayed(checkInterval);
- }
- }
-
- if (DateTime.now().difference(startTime) >= timeout) {
-
- coreStatus.value = RunningState.error;
- controllers.global.updateMsg("内核启动超时,重新点击加速后尝试。");
- return false;
- }
- await controllers.core.updateConfig();
- coreStatus.value = RunningState.running;
- controllers.global.updateMsg("内核状态:${coreStatus.value == RunningState.running} ");
- return true;
- } catch (e) {
- log.error("core -- $e");
- controllers.global.updateMsg("启动内核错误");
-
-
- coreStatus.value = RunningState.error;
- return false;
- }
- }
- Future<void> stopClashCore() async {
- coreStatus.value = RunningState.stopping;
- await controllers.global.closeProxy();
- if(serviceMode.value == true){
- await fetchStop();
- }
- else{
- if(clashCoreProcess != null){
- clashCoreProcess?.kill();
- }
- }
- killProcess(ClashName.name);
-
-
-
-
-
-
-
-
- coreStatus.value = RunningState.stoped;
- }
- Future<void> initClashCoreConfig() async {
- controllers.config.config.value.selected = 'init_proxy.yaml';
-
- await startClashCore();
- if (coreStatus.value == RunningState.error) {
- controllers.global.updateMsg("启动内核失败...");
-
- } else {
- await controllers.core.updateVersion();
- controllers.global.updateMsg("启动内核成功...");
-
- }
- }
- Future<void> stopClash() async {
- controllers.config.config.value.selected = 'init_proxy.yaml';
- if( coreStatus.value == RunningState.running){
- await controllers.config.readClashCoreApi();
- controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value);
- await controllers.core.changeConfig(path.join(Paths.config.path, controllers.config.config.value.selected));
- }
- }
- Future<void> reloadClashCore() async {
- try
- {
- controllers.config.config.value.selected = 'proxy.yaml';
- if( coreStatus.value == RunningState.running){
- controllers.global.updateMsg("切换配置...");
- await controllers.config.readClashCoreApi();
-
- controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value);
-
- await controllers.core.changeConfig(path.join(Paths.config.path, controllers.config.config.value.selected));
- controllers.global.updateMsg("fetchReloadConfig${controllers.config.clashCoreApiAddress.value}...");
- }
- } catch(e){
- controllers.global.updateMsg("重新配置...");
- await controllers.config.readClashCoreApi();
-
- controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value);
-
- await controllers.core.changeConfig(path.join(Paths.config.path, controllers.config.config.value.selected));
- }
-
-
-
-
-
-
-
-
-
-
-
- }
- Future<int> getFreePort() async {
- var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
- int port = server.port;
- await server.close();
- return port;
- }
- }
|