From e56e67bd44caa72338848be1a0bebdfe4724ca7d Mon Sep 17 00:00:00 2001 From: Hiddify Date: Fri, 2 Feb 2024 17:35:32 +0100 Subject: [PATCH] new: add tunnel service for windows and linux --- .github/workflows/build.yml | 6 +- Makefile | 31 +++-- .../data/connection_platform_source.dart | 122 ++++++++++++++++++ .../data/connection_repository.dart | 44 ++++++- linux/CMakeLists.txt | 3 + windows/CMakeLists.txt | 4 + 6 files changed, 188 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a4547f2f..bffc4b80 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,19 +40,19 @@ jobs: filename: hiddify-windows-x64 - platform: linux-appimage - os: ubuntu-latest + os: ubuntu-20.04 aarch: amd64 targets: AppImage filename: hiddify-linux-x64 - platform: linux-deb - os: ubuntu-latest + os: ubuntu-20.04 aarch: amd64 targets: deb filename: hiddify-debian-x64 - platform: linux-rpm - os: ubuntu-latest + os: ubuntu-20.04 aarch: amd64 targets: rpm filename: hiddify-rpm-x64 diff --git a/Makefile b/Makefile index c67601be..e8308cdd 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,9 @@ IOS_OUT=./ios/Frameworks DESKTOP_OUT=./libcore/bin GEO_ASSETS_DIR=./assets/core -CORE_PRODUCT_NAME=libcore -CORE_NAME=hiddify-$(CORE_PRODUCT_NAME) +CORE_PRODUCT_NAME=hiddify-core +CORE_NAME=$(CORE_PRODUCT_NAME) +SRV_NAME=hiddify-service ifeq ($(CHANNEL),prod) CORE_URL=https://github.com/hiddify/hiddify-next-core/releases/download/v$(core.version) else @@ -81,18 +82,18 @@ ios-release: #not tested android-libs: mkdir -p $(ANDROID_OUT) - curl -L $(CORE_URL)/$(CORE_NAME)-android.aar.gz | gunzip > $(ANDROID_OUT)/libcore.aar + curl -L $(CORE_URL)/$(CORE_NAME)-android.tar.gz | tar xz -C $(ANDROID_OUT)/ android-apk-libs: android-libs android-aab-libs: android-libs windows-libs: mkdir -p $(DESKTOP_OUT) - curl -L $(CORE_URL)/$(CORE_NAME)-windows-amd64.dll.gz | gunzip > $(DESKTOP_OUT)/libcore.dll + curl -L $(CORE_URL)/$(CORE_NAME)-windows-amd64.tar.gz | tar xz -C $(DESKTOP_OUT)/ linux-libs: mkdir -p $(DESKTOP_OUT) - curl -L $(CORE_URL)/$(CORE_NAME)-linux-amd64.so.gz | gunzip > $(DESKTOP_OUT)/libcore.so + curl -L $(CORE_URL)/$(CORE_NAME)-linux-amd64.tar.gz | tar xz -C $(DESKTOP_OUT)/ linux-deb-libs:linux-libs @@ -101,13 +102,12 @@ linux-appimage-libs:linux-libs macos-libs: mkdir -p $(DESKTOP_OUT)/ &&\ - curl -L $(CORE_URL)/$(CORE_NAME)-macos-universal.dylib.gz | gunzip > $(DESKTOP_OUT)/libcore.dylib + curl -L $(CORE_URL)/$(CORE_NAME)-macos-universal.tar.gz | tar xz -C $(DESKTOP_OUT)/ ios-libs: #not tested mkdir -p $(DESKTOP_OUT)/ && \ rm -rf $(IOS_OUT)/Libcore.xcframework && \ - curl -L $(CORE_URL)/$(CORE_NAME)-ios.xcframework.tar.gz | tar xz -C "$(IOS_OUT)" && \ - mv $(IOS_OUT)/$(CORE_NAME)-ios.xcframework $(IOS_OUT)/Libcore.xcframework + curl -L $(CORE_URL)/$(CORE_NAME)-ios.tar.gz | tar xz -C "$(IOS_OUT)" get-geo-assets: curl -L https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db -o $(GEO_ASSETS_DIR)/geoip.db @@ -117,16 +117,23 @@ build-headers: make -C libcore -f Makefile headers && mv $(BINDIR)/$(CORE_NAME)-headers.h $(BINDIR)/libcore.h build-android-libs: - make -C libcore -f Makefile android && mv $(BINDIR)/$(CORE_NAME)-android.aar $(ANDROID_OUT)/libcore.aar + make -C libcore -f Makefile android + mv $(BINDIR)/$(CORE_NAME).aar $(ANDROID_OUT)/ build-windows-libs: - make -C libcore -f Makefile windows-amd64 && mv $(BINDIR)/$(CORE_NAME)-windows-amd64.dll $(DESKTOP_OUT)/libcore.dll + make -C libcore -f Makefile windows-amd64 + mv $(BINDIR)/$(CORE_NAME).dll $(DESKTOP_OUT)/ + mv $(BINDIR)/$(SRV_NAME) $(DESKTOP_OUT)/ build-linux-libs: - make -C libcore -f Makefile linux-amd64 && mv $(BINDIR)/$(CORE_NAME)-linux-amd64.so $(DESKTOP_OUT)/libcore.so + make -C libcore -f Makefile linux-amd64 + mv $(BINDIR)/$(CORE_NAME).so $(DESKTOP_OUT)/ + mv $(BINDIR)/$(SRV_NAME) $(DESKTOP_OUT)/ build-macos-libs: - make -C libcore -f Makefile macos-universal && mv $(BINDIR)/$(CORE_NAME)-macos-universal.dylib $(DESKTOP_OUT)/libcore.dylib + make -C libcore -f Makefile macos-universal + mv $(BINDIR)/$(CORE_NAME).dylib $(DESKTOP_OUT)/ + mv $(BINDIR)/$(SRV_NAME) $(DESKTOP_OUT)/ build-ios-libs: rm -rf $(IOS_OUT)/Libcore.xcframework && \ diff --git a/lib/features/connection/data/connection_platform_source.dart b/lib/features/connection/data/connection_platform_source.dart index 7304abb6..be842e83 100644 --- a/lib/features/connection/data/connection_platform_source.dart +++ b/lib/features/connection/data/connection_platform_source.dart @@ -1,13 +1,19 @@ +import 'dart:async'; +import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; import 'package:hiddify/core/utils/ffi_utils.dart'; import 'package:hiddify/utils/custom_loggers.dart'; +import 'package:hiddify/utils/utils.dart'; +import 'package:path/path.dart' as p; import 'package:posix/posix.dart'; import 'package:win32/win32.dart'; abstract interface class ConnectionPlatformSource { Future checkPrivilege(); + Future activateTunnel(); + Future deactivateTunnel(); } class ConnectionPlatformSourceImpl @@ -57,6 +63,122 @@ class ConnectionPlatformSourceImpl return true; // return true so core handles it } } + + @override + Future activateTunnel() async { + if (!Platform.isWindows && !Platform.isLinux && !Platform.isMacOS) { + return true; + } + try { + final socket = await Socket.connect('127.0.0.1', 18020, + timeout: Duration(seconds: 1)); + await socket.close(); + return await startTunnelRequest(); + } catch (error) { + loggy.warning( + 'Tunnel Service is not running. Error: $error.--> Running...'); + return await runTunnelService(); + } + } + + @override + Future deactivateTunnel() async { + if (!Platform.isWindows && !Platform.isLinux && !Platform.isMacOS) { + return true; + } + try { + return await stopTunnelRequest(); + } catch (error) { + loggy.error('Tunnel Service Stop Error: $error.'); + return false; + } + } + + Future startTunnelRequest() async { + final params = { + "Ipv6": false, + "ServerPort": "2334", + "StrictRoute": false, + "EndpointIndependentNat": false, + "Stack": "gvisor", + }; + + final query = mapToQueryString(params); + + try { + final request = + await HttpClient().get('localhost', 18020, "/start?$query"); + final response = await request.close(); + final body = await response.transform(utf8.decoder).join(); + loggy.debug( + 'Status Code: ${response.statusCode} ${response.reasonPhrase}'); + loggy.debug('Response Body: ${body}'); + return true; + } catch (error) { + loggy.error('HTTP Request Error: $error'); + return false; + } + } + + Future stopTunnelRequest() async { + try { + final request = await HttpClient().get('localhost', 18020, "/stop"); + final response = await request.close(); + final body = await response.transform(utf8.decoder).join(); + loggy.debug( + 'Status Code: ${response.statusCode} ${response.reasonPhrase}'); + loggy.debug('Response Body: ${body}'); + return true; + } catch (error) { + loggy.error('HTTP Request Error: $error'); + return false; + } + } + + String mapToQueryString(Map params) { + return params.entries.map((entry) { + final key = Uri.encodeQueryComponent(entry.key); + final value = Uri.encodeQueryComponent(entry.value.toString()); + return '$key=$value'; + }).join('&'); + } + + Future runTunnelService() async { + final executablePath = getTunnelServicePath(); + + var command = [executablePath, "install"]; + if (Platform.isLinux) { + command.insert(0, 'pkexec'); + } + + try { + final result = + await Process.run(command[0], command.sublist(1), runInShell: true); + loggy.debug('Shell command executed: ${result.stdout} ${result.stderr}'); + return await startTunnelRequest(); + } catch (error) { + loggy.error('Error executing shell command: $error'); + return false; + } + } + + static String getTunnelServicePath() { + String fullPath = ""; + final binFolder = + Directory(Platform.resolvedExecutable).parent.absolute.path; + if (Platform.environment.containsKey('FLUTTER_TEST')) { + fullPath = "libcore"; + } + if (Platform.isWindows) { + fullPath = p.join(fullPath, "hiddify-service.exe"); + } else if (Platform.isMacOS) { + fullPath = p.join(fullPath, "hiddify-service"); + } else { + fullPath = p.join(fullPath, "hiddify-service"); + } + + return "$binFolder/$fullPath"; + } } sealed class _TokenElevation extends Struct { diff --git a/lib/features/connection/data/connection_repository.dart b/lib/features/connection/data/connection_repository.dart index 71c5ee69..57eb892e 100644 --- a/lib/features/connection/data/connection_repository.dart +++ b/lib/features/connection/data/connection_repository.dart @@ -159,11 +159,16 @@ class ConnectionRepositoryImpl await $( TaskEither(() async { if (options.enableTun) { - final hasPrivilege = await platformSource.checkPrivilege(); - if (!hasPrivilege) { - loggy.warning("missing privileges for tun mode"); + final active = await platformSource.activateTunnel(); + if (!active) { + loggy.warning("Possiblity missing privileges for tun mode"); return left(const MissingPrivilege()); } + // final hasPrivilege = await platformSource.checkPrivilege(); + // if (!hasPrivilege) { + // loggy.warning("missing privileges for tun mode"); + // return left(const MissingPrivilege()); + // } } return right(unit); }), @@ -185,10 +190,35 @@ class ConnectionRepositoryImpl @override TaskEither disconnect() { - return exceptionHandler( - () => singbox.stop().mapLeft(UnexpectedConnectionFailure.new).run(), - UnexpectedConnectionFailure.new, - ); + return TaskEither.Do( + ($) async { + final options = await $(getConfigOption()); + + await $( + TaskEither(() async { + if (options.enableTun) { + final active = await platformSource.deactivateTunnel(); + if (!active) { + loggy.warning("Possiblity missing privileges for tun mode"); + return left(const MissingPrivilege()); + } + // final hasPrivilege = await platformSource.checkPrivilege(); + // if (!hasPrivilege) { + // loggy.warning("missing privileges for tun mode"); + // return left(const MissingPrivilege()); + // } + } + return right(unit); + }), + ); + return await $( + singbox.stop() + .mapLeft(UnexpectedConnectionFailure.new), + ); + }, + ).handleExceptions(UnexpectedConnectionFailure.new); + + } @override diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 521decf0..398f3343 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -120,6 +120,9 @@ install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "../libcore/bin/libcore.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) +install(FILES "../libcore/bin/hiddify-service" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) install(FILES "${bundled_library}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 73973a4b..1a1e60df 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -87,6 +87,10 @@ set(HIDDIFY_NEXT_LIB "../libcore/bin/libcore.dll") install(FILES "${HIDDIFY_NEXT_LIB}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime RENAME libcore.dll) +set(HIDDIFY_NEXT_LIB "../libcore/bin/hiddify-service.exe") +install(FILES "${HIDDIFY_NEXT_LIB}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" +COMPONENT Runtime RENAME hiddify-service.exe) + if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"