vpn_windows_service.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'package:dio/dio.dart';
  5. import 'package:shelf/shelf_io.dart' as shelf_io;
  6. import 'package:shelf_web_socket/shelf_web_socket.dart';
  7. import '../../../utils/crypto.dart';
  8. import '../../../utils/log/logger.dart';
  9. import '../../../utils/misc.dart';
  10. import '../../constants/enums.dart';
  11. import 'vpn_exception.dart';
  12. import 'vpn_message.dart';
  13. class _RpcResult {
  14. final bool success;
  15. final String message;
  16. final dynamic data;
  17. _RpcResult(this.success, this.message, this.data);
  18. }
  19. class VpnWindowsService {
  20. static const _tag = 'VpnWindowsService';
  21. static const _rpcPort = 29764;
  22. static const _rpcFileName = 'ixrpc.exe';
  23. static const _rpcHeartbeatTimeout = 5;
  24. static const _rpcBaseUrl = 'http://127.0.0.1:$_rpcPort';
  25. static const String _accessKeySecret =
  26. '{C5E098D8-5487-4F8A-8B23-7F1F839A3A0B}';
  27. final _rpcDio = Dio();
  28. ConnectionState _status = ConnectionState.disconnected;
  29. final _ecStatus = StreamController<ConnectionState>.broadcast();
  30. bool _isOnline = false;
  31. final _ecOnline = StreamController<bool>.broadcast();
  32. Process? _process;
  33. Timer? _refreshTimer;
  34. Timer? _startupTimer;
  35. String? _logPath;
  36. bool _vpnDebug = false;
  37. bool _isStarted = false;
  38. int _rpcHostPort = 0;
  39. DateTime _lastAliveTime = DateTime.now();
  40. bool _needRecoverConnection = false;
  41. Map<String, dynamic>? _connectionParams;
  42. void _setStatus(ConnectionState value) {
  43. if (_status != value) {
  44. _status = value;
  45. _ecStatus.add(_status);
  46. }
  47. }
  48. void _setIsOnline(bool value) {
  49. if (_isOnline != value) {
  50. _isOnline = value;
  51. _ecOnline.add(_isOnline);
  52. }
  53. }
  54. Future<_RpcResult> _rpcGet(String action) async {
  55. final url = Uri.parse('$_rpcBaseUrl/api/$action').toString();
  56. final response = await _rpcDio.get(url);
  57. if (response.statusCode != 200) {
  58. log(_tag, 'rpcGet HTTP error: ${response.statusCode}');
  59. return _RpcResult(false, 'HTTP ${response.statusCode}', null);
  60. }
  61. final json = response.data as Map<String, dynamic>;
  62. final int code = json['code'] as int;
  63. final String msg = json['msg'] as String;
  64. return _RpcResult(code == 200, msg, json);
  65. }
  66. Future<_RpcResult> _rpcPost(
  67. String action,
  68. Map<String, dynamic> params,
  69. ) async {
  70. if (!isOnline) {
  71. throw RpcException('Vpn rpc service not available');
  72. }
  73. final url = Uri.parse('$_rpcBaseUrl/api/action').toString();
  74. final data = jsonEncode({'action': action, 'data': params});
  75. try {
  76. final timestamp = DateTime.now().millisecondsSinceEpoch;
  77. final signature = await Crypto.signature(
  78. '$data:$timestamp',
  79. _accessKeySecret,
  80. );
  81. log(_tag, '[VpnWindowsService] url:$url');
  82. log(_tag, '[VpnWindowsService] >>:$data');
  83. final response = await _rpcDio.post(
  84. url,
  85. data: data,
  86. options: Options(
  87. headers: {
  88. 'Accept': 'application/json',
  89. 'Timestamp': '$timestamp',
  90. 'Signature': signature,
  91. },
  92. ),
  93. );
  94. log(_tag, '[VpnWindowsService] <<:${response.data}');
  95. if (response.statusCode != 200) {
  96. log(_tag, 'rpcPost HTTP error: ${response.statusCode}');
  97. return _RpcResult(false, 'HTTP ${response.statusCode}', null);
  98. }
  99. final json = response.data as Map<String, dynamic>; // Dio 自动反序列化JSON的数据
  100. final int code = json['code'] as int;
  101. final String msg = json['msg'] as String;
  102. return _RpcResult(code == 200, msg, json);
  103. } on DioException catch (e, stack) {
  104. log(_tag, 'DioException: ${e.message}\nStack: $stack');
  105. throw RpcException('DioException: ${e.message}');
  106. } catch (e, stack) {
  107. log(_tag, 'request error: $e\nStack: $stack');
  108. throw RpcException('request error: $e');
  109. }
  110. }
  111. Future<void> _check() {
  112. return _rpcGet('ping');
  113. }
  114. Future<void> _resetProcess(int hostPort, bool debug, {int? rpcPort}) async {
  115. try {
  116. _killProcess();
  117. final List<String> args = [];
  118. args.add('-l');
  119. args.add('127.0.0.1:$hostPort');
  120. if (rpcPort != null) {
  121. args.add('-p');
  122. args.add(rpcPort.toString());
  123. }
  124. if (_logPath != null) {
  125. args.add('-d');
  126. args.add(_logPath!);
  127. }
  128. log(_tag, '_resetProcess: starting $_rpcFileName with args: $args');
  129. _process = await Process.start(
  130. _rpcFileName,
  131. args,
  132. runInShell: false,
  133. mode: ProcessStartMode.detached,
  134. );
  135. // _process?.stdout.drain();
  136. // _process?.stderr.drain();
  137. log(_tag, '_resetProcess: process started successfully.');
  138. } catch (e, stack) {
  139. log(_tag, '_resetProcess error: $e\nStack: $stack');
  140. }
  141. }
  142. void _killProcess() {
  143. log(_tag, 'VpnWindowsService kill process');
  144. _process?.kill();
  145. _process = null;
  146. _setIsOnline(false);
  147. }
  148. void dispose() async {
  149. log(_tag, 'VpnWindowsService dispose start.');
  150. await _shutdown();
  151. //_ecTraffic.close();
  152. _ecStatus.close();
  153. _ecOnline.close();
  154. }
  155. Future<void> start(Map<String, dynamic> params) async {
  156. _connectionParams = null;
  157. // 通知正在连接
  158. _setStatus(ConnectionState.connecting);
  159. try {
  160. final result = await _rpcPost('connect', params);
  161. if (!result.success) {
  162. log(_tag, 'start call error: ${result.message}');
  163. _setStatus(ConnectionState.error);
  164. } else {
  165. _connectionParams = params;
  166. log(_tag, 'start call success.');
  167. }
  168. } catch (e, stack) {
  169. log(_tag, 'start call exception: $e\nStack: $stack');
  170. // 通知连接失败
  171. _setStatus(ConnectionState.error);
  172. }
  173. }
  174. Future<void> stop() async {
  175. _needRecoverConnection = false;
  176. try {
  177. final result = await _rpcPost('disconnect', {});
  178. if (result.success) {
  179. log(_tag, 'disconnect call success.');
  180. } else {
  181. log(_tag, 'disconnect call error: ${result.message}');
  182. }
  183. } catch (e, stack) {
  184. log(_tag, 'disconnect call exception: $e\nStack: $stack');
  185. }
  186. }
  187. Future<String> getLocationName() async {
  188. return '';
  189. }
  190. Future<String> getRemoteAddress() async {
  191. try {
  192. final result = await _rpcPost('get_remote_ip', {});
  193. if (!result.success) {
  194. log(_tag, 'getRemoteAddress error: ${result.message}');
  195. return '';
  196. }
  197. return result.message;
  198. } catch (e) {
  199. log(_tag, 'getRemoteAddress error: $e');
  200. return '';
  201. }
  202. }
  203. Future<bool> setBypassAddresses(List<String> list) async {
  204. try {
  205. if (list.isEmpty) {
  206. log(_tag, 'setBypassAddresses: empty list');
  207. return false;
  208. }
  209. final result = await _rpcPost('set_pass_ips', {'ips': list.join(',')});
  210. if (!result.success) {
  211. log(_tag, 'setBypassAddresses error: ${result.message}');
  212. return false;
  213. }
  214. return true;
  215. } catch (e) {
  216. log(_tag, 'setBypassAddresses error: $e');
  217. return false;
  218. }
  219. }
  220. Future<void> _startup({bool debug = false}) async {
  221. log(_tag, 'startup called. debug: $debug');
  222. _vpnDebug = debug;
  223. if (_isStarted) {
  224. log(_tag, 'startup abort! _isStarted: $_isStarted');
  225. return;
  226. }
  227. try {
  228. // 处理VPN 消息
  229. final handler = webSocketHandler((webSocket, _) {
  230. webSocket.stream.listen((message) {
  231. _lastAliveTime = DateTime.now();
  232. try {
  233. final vpnMessage = VpnMessage.fromMap(jsonDecode(message));
  234. switch (vpnMessage.type) {
  235. case VpnMessageType.login:
  236. log(_tag, 'login message got');
  237. // rpc已经上线
  238. _setIsOnline(true);
  239. // 保存重连状态
  240. try {
  241. if (_status == ConnectionState.connected ||
  242. _status == ConnectionState.connecting) {
  243. _needRecoverConnection = true;
  244. }
  245. } catch (e, stack) {
  246. log(_tag, 'login Exception: $e\nStack: $stack');
  247. }
  248. break;
  249. case VpnMessageType.heartbeat:
  250. log(_tag, 'heartbeat message got');
  251. // 应答心跳
  252. webSocket.sink.add(
  253. VpnMessage.create(VpnMessageType.heartbeat, null).toJson(),
  254. );
  255. break;
  256. case VpnMessageType.stateSync:
  257. log(_tag, 'stateSync message got');
  258. try {
  259. final data = vpnMessage.data;
  260. if (data == null) {
  261. return;
  262. }
  263. final int state = data['state'] as int;
  264. //final dynamic param = data['param']; // 错误code
  265. _setStatus(ConnectionState.values[state]);
  266. // 恢复连接
  267. if (_needRecoverConnection) {
  268. if (_connectionParams != null) {
  269. start(_connectionParams!);
  270. }
  271. _needRecoverConnection = false;
  272. }
  273. } catch (e, stack) {
  274. log(_tag, 'stateSync Exception: $e\nStack: $stack');
  275. }
  276. break;
  277. default:
  278. }
  279. } catch (e, stack) {
  280. log(_tag, 'webSocketHandler Exception: $e\nStack: $stack');
  281. }
  282. });
  283. });
  284. shelf_io.serve(handler, '127.0.0.1', 35461).then((server) async {
  285. _isStarted = true;
  286. log(_tag, 'startup finished. _isStarted: $_isStarted');
  287. log(_tag, 'Serving at ws://${server.address.host}:${server.port}');
  288. // websocket 服务器端
  289. _rpcHostPort = server.port;
  290. // 启动vpn客户端进程
  291. await _resetProcess(_rpcHostPort, rpcPort: _rpcPort, _vpnDebug);
  292. // 启动守护定时器
  293. _refreshTimer = Timer.periodic(const Duration(seconds: 1), (_) async {
  294. final dt = DateTime.now().difference(_lastAliveTime).inSeconds;
  295. if (dt > _rpcHeartbeatTimeout) {
  296. // 尝试check一下
  297. try {
  298. await _check();
  299. _lastAliveTime = DateTime.now();
  300. log(_tag, 'RPC process has been woken up, continuing...');
  301. } catch (e) {
  302. log(_tag, 'RPC process has timedout, need reset.');
  303. _lastAliveTime = DateTime.now();
  304. await _resetProcess(_rpcHostPort, rpcPort: _rpcPort, _vpnDebug);
  305. }
  306. }
  307. });
  308. });
  309. } catch (e, stack) {
  310. log(_tag, 'startup error: $e\nStack: $stack');
  311. }
  312. }
  313. Future<void> _shutdown() async {
  314. log(_tag, 'shutdown called.');
  315. if (!_isStarted) {
  316. log(_tag, 'shutdown abort! _isStarted: $_isStarted');
  317. return;
  318. }
  319. try {
  320. // 断开连接
  321. await stop();
  322. } catch (e, stack) {
  323. log(_tag, 'shutdown disconnect error: $e\nStack: $stack');
  324. }
  325. try {
  326. // 关闭守护定时器
  327. _startupTimer?.cancel();
  328. } catch (e, stack) {
  329. log(_tag, 'shutdown _startupTimer cancel error: $e\nStack: $stack');
  330. }
  331. try {
  332. _refreshTimer?.cancel();
  333. } catch (e, stack) {
  334. log(_tag, 'shutdown _refreshTimer cancel error: $e\nStack: $stack');
  335. }
  336. try {
  337. // 关闭vpn客户端进程
  338. _killProcess();
  339. } catch (e, stack) {
  340. log(_tag, 'shutdown _killProcess error: $e\nStack: $stack');
  341. }
  342. _isStarted = false;
  343. _needRecoverConnection = false;
  344. _setStatus(ConnectionState.disconnected);
  345. log(_tag, 'shutdown finished. _isStarted: $_isStarted');
  346. }
  347. Future<void> initialize(int timedout, bool debug) async {
  348. log(_tag, 'vpn service initialize...');
  349. _logPath = (await logFileDirectory()).path;
  350. final startTime = DateTime.now();
  351. bool isTimedout = false;
  352. // 启动服务
  353. await _startup(debug: debug);
  354. // 等待vpn客户端就绪信号
  355. if (!isOnline) {
  356. final completer = Completer();
  357. Timer.periodic(const Duration(milliseconds: 500), (timer) {
  358. // 客户端就绪
  359. if (isOnline) {
  360. log(_tag, 'vpn service start successed.');
  361. completer.complete();
  362. timer.cancel();
  363. }
  364. // 等待超时
  365. final dt = DateTime.now().difference(startTime).inSeconds;
  366. if (dt > timedout) {
  367. log(_tag, 'vpn service start timedout.');
  368. completer.complete();
  369. timer.cancel();
  370. isTimedout = true;
  371. }
  372. });
  373. await completer.future;
  374. log(_tag, 'vpn service initialize finished.');
  375. }
  376. if (isTimedout) {
  377. _shutdown();
  378. throw WaitOnlineTimedOutException();
  379. }
  380. }
  381. bool get isOnline => _isOnline;
  382. ConnectionState get status => _status;
  383. Stream<bool> get onOnlineChanged => _ecOnline.stream;
  384. Stream<ConnectionState> get onStatusChanged => _ecStatus.stream;
  385. }