commit 3ac7a5332293c43c51cc2fa7cba1b62a8020bfbd Author: Stossy11 <69031796+stossy11@users.noreply.github.com> Date: Mon Mar 31 13:40:08 2025 +1100 first commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..f91fe84 Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9362e7e --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# StosVPN + +A VPN for SideStore and StikJIT that is much stabler and supports offline JIT Enabling. diff --git a/StosVPN.xcodeproj/project.pbxproj b/StosVPN.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b6c06a7 --- /dev/null +++ b/StosVPN.xcodeproj/project.pbxproj @@ -0,0 +1,521 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 70; + objects = { + +/* Begin PBXBuildFile section */ + 4EB3C7712D96715400C1B22C /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4EB3C7702D96715400C1B22C /* NetworkExtension.framework */; }; + 4EB3C7792D96715400C1B22C /* TunnelProv.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4EB3C76E2D96715400C1B22C /* TunnelProv.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 4EB3C7772D96715400C1B22C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4EB3C7502D96631A00C1B22C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4EB3C76D2D96715400C1B22C; + remoteInfo = TunnelProv; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 4EB3C77E2D96715400C1B22C /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 4EB3C7792D96715400C1B22C /* TunnelProv.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 4EB3C7582D96631A00C1B22C /* StosVPN.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StosVPN.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 4EB3C76E2D96715400C1B22C /* TunnelProv.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TunnelProv.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 4EB3C7702D96715400C1B22C /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 4EB3C77A2D96715400C1B22C /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 4EB3C76D2D96715400C1B22C /* TunnelProv */; + }; + 4EB3C7802D9672DE00C1B22C /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 4EB3C7572D96631A00C1B22C /* StosVPN */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 4EB3C75A2D96631A00C1B22C /* StosVPN */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (4EB3C7802D9672DE00C1B22C /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = StosVPN; sourceTree = ""; }; + 4EB3C7722D96715400C1B22C /* TunnelProv */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (4EB3C77A2D96715400C1B22C /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = TunnelProv; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4EB3C7552D96631A00C1B22C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4EB3C76B2D96715400C1B22C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4EB3C7712D96715400C1B22C /* NetworkExtension.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4EB3C74F2D96631A00C1B22C = { + isa = PBXGroup; + children = ( + 4EB3C75A2D96631A00C1B22C /* StosVPN */, + 4EB3C7722D96715400C1B22C /* TunnelProv */, + 4EB3C76F2D96715400C1B22C /* Frameworks */, + 4EB3C7592D96631A00C1B22C /* Products */, + ); + sourceTree = ""; + }; + 4EB3C7592D96631A00C1B22C /* Products */ = { + isa = PBXGroup; + children = ( + 4EB3C7582D96631A00C1B22C /* StosVPN.app */, + 4EB3C76E2D96715400C1B22C /* TunnelProv.appex */, + ); + name = Products; + sourceTree = ""; + }; + 4EB3C76F2D96715400C1B22C /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4EB3C7702D96715400C1B22C /* NetworkExtension.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 4EB3C7572D96631A00C1B22C /* StosVPN */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4EB3C7662D96631B00C1B22C /* Build configuration list for PBXNativeTarget "StosVPN" */; + buildPhases = ( + 4EB3C7542D96631A00C1B22C /* Sources */, + 4EB3C7552D96631A00C1B22C /* Frameworks */, + 4EB3C7562D96631A00C1B22C /* Resources */, + 4EB3C77E2D96715400C1B22C /* Embed Foundation Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 4EB3C7782D96715400C1B22C /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 4EB3C75A2D96631A00C1B22C /* StosVPN */, + ); + name = StosVPN; + packageProductDependencies = ( + ); + productName = StosVPN; + productReference = 4EB3C7582D96631A00C1B22C /* StosVPN.app */; + productType = "com.apple.product-type.application"; + }; + 4EB3C76D2D96715400C1B22C /* TunnelProv */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4EB3C77B2D96715400C1B22C /* Build configuration list for PBXNativeTarget "TunnelProv" */; + buildPhases = ( + 4EB3C76A2D96715400C1B22C /* Sources */, + 4EB3C76B2D96715400C1B22C /* Frameworks */, + 4EB3C76C2D96715400C1B22C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 4EB3C7722D96715400C1B22C /* TunnelProv */, + ); + name = TunnelProv; + packageProductDependencies = ( + ); + productName = TunnelProv; + productReference = 4EB3C76E2D96715400C1B22C /* TunnelProv.appex */; + productType = "com.apple.product-type.app-extension"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4EB3C7502D96631A00C1B22C /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1620; + LastUpgradeCheck = 1620; + ORGANIZATIONNAME = stossy11; + TargetAttributes = { + 4EB3C7572D96631A00C1B22C = { + CreatedOnToolsVersion = 16.2; + LastSwiftMigration = 1620; + }; + 4EB3C76D2D96715400C1B22C = { + CreatedOnToolsVersion = 16.2; + }; + }; + }; + buildConfigurationList = 4EB3C7532D96631A00C1B22C /* Build configuration list for PBXProject "StosVPN" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 4EB3C74F2D96631A00C1B22C; + minimizedProjectReferenceProxies = 1; + productRefGroup = 4EB3C7592D96631A00C1B22C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4EB3C7572D96631A00C1B22C /* StosVPN */, + 4EB3C76D2D96715400C1B22C /* TunnelProv */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4EB3C7562D96631A00C1B22C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4EB3C76C2D96715400C1B22C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4EB3C7542D96631A00C1B22C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4EB3C76A2D96715400C1B22C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 4EB3C7782D96715400C1B22C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4EB3C76D2D96715400C1B22C /* TunnelProv */; + targetProxy = 4EB3C7772D96715400C1B22C /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 4EB3C7642D96631B00C1B22C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 4EB3C7652D96631B00C1B22C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 4EB3C7672D96631B00C1B22C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = StosVPN/StosVPN.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"StosVPN/Preview Content\""; + DEVELOPMENT_TEAM = 95J8WZ4TN8; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = StosVPN/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.StosVPN; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "StosVPN/StosVPN-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 4EB3C7682D96631B00C1B22C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = StosVPN/StosVPN.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"StosVPN/Preview Content\""; + DEVELOPMENT_TEAM = 95J8WZ4TN8; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = StosVPN/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.StosVPN; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "StosVPN/StosVPN-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 4EB3C77C2D96715400C1B22C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = TunnelProv/TunnelProv.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 95J8WZ4TN8; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = TunnelProv/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = TunnelProv; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.StosVPN.TunnelProv; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "StosVPN/StosVPN-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 4EB3C77D2D96715400C1B22C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = TunnelProv/TunnelProv.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 95J8WZ4TN8; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = TunnelProv/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = TunnelProv; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.StosVPN.TunnelProv; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "StosVPN/StosVPN-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4EB3C7532D96631A00C1B22C /* Build configuration list for PBXProject "StosVPN" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4EB3C7642D96631B00C1B22C /* Debug */, + 4EB3C7652D96631B00C1B22C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4EB3C7662D96631B00C1B22C /* Build configuration list for PBXNativeTarget "StosVPN" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4EB3C7672D96631B00C1B22C /* Debug */, + 4EB3C7682D96631B00C1B22C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4EB3C77B2D96715400C1B22C /* Build configuration list for PBXNativeTarget "TunnelProv" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4EB3C77C2D96715400C1B22C /* Debug */, + 4EB3C77D2D96715400C1B22C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 4EB3C7502D96631A00C1B22C /* Project object */; +} diff --git a/StosVPN.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/StosVPN.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/StosVPN.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/StosVPN.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate b/StosVPN.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..0d8ac2e Binary files /dev/null and b/StosVPN.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/StosVPN.xcodeproj/xcshareddata/xcschemes/StosVPN.xcscheme b/StosVPN.xcodeproj/xcshareddata/xcschemes/StosVPN.xcscheme new file mode 100644 index 0000000..41fa216 --- /dev/null +++ b/StosVPN.xcodeproj/xcshareddata/xcschemes/StosVPN.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StosVPN.xcodeproj/xcshareddata/xcschemes/TunnelProv.xcscheme b/StosVPN.xcodeproj/xcshareddata/xcschemes/TunnelProv.xcscheme new file mode 100644 index 0000000..31c9da5 --- /dev/null +++ b/StosVPN.xcodeproj/xcshareddata/xcschemes/TunnelProv.xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StosVPN.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/StosVPN.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..a97bd34 --- /dev/null +++ b/StosVPN.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/StosVPN.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcschemes/xcschememanagement.plist b/StosVPN.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..ee4d95c --- /dev/null +++ b/StosVPN.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,32 @@ + + + + + SchemeUserState + + StosVPN.xcscheme_^#shared#^_ + + orderHint + 0 + + TunnelProv.xcscheme_^#shared#^_ + + orderHint + 1 + + + SuppressBuildableAutocreation + + 4EB3C7572D96631A00C1B22C + + primary + + + 4EB3C76D2D96715400C1B22C + + primary + + + + + diff --git a/StosVPN/Assets.xcassets/.DS_Store b/StosVPN/Assets.xcassets/.DS_Store new file mode 100644 index 0000000..1943592 Binary files /dev/null and b/StosVPN/Assets.xcassets/.DS_Store differ diff --git a/StosVPN/Assets.xcassets/AccentColor.colorset/Contents.json b/StosVPN/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/StosVPN/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StosVPN/Assets.xcassets/AppIcon.appiconset/Contents.json b/StosVPN/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2c36209 --- /dev/null +++ b/StosVPN/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "StosVPN-icon.jpg", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StosVPN/Assets.xcassets/AppIcon.appiconset/StosVPN-icon.jpg b/StosVPN/Assets.xcassets/AppIcon.appiconset/StosVPN-icon.jpg new file mode 100644 index 0000000..9dc50d5 Binary files /dev/null and b/StosVPN/Assets.xcassets/AppIcon.appiconset/StosVPN-icon.jpg differ diff --git a/StosVPN/Assets.xcassets/Contents.json b/StosVPN/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/StosVPN/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StosVPN/ContentView.swift b/StosVPN/ContentView.swift new file mode 100644 index 0000000..c7768f5 --- /dev/null +++ b/StosVPN/ContentView.swift @@ -0,0 +1,732 @@ +// +// ContentView.swift +// StosVPN +// +// Created by Stossy11 on 28/03/2025. +// + +import SwiftUI +import Foundation +import NetworkExtension + + +// MARK: - Logging Utility +class VPNLogger: ObservableObject { + @Published var logs: [String] = [] + + static var shared = VPNLogger() + + private init() {} + + func log(_ message: Any, file: String = #file, function: String = #function, line: Int = #line) { + #if DEBUG + let fileName = (file as NSString).lastPathComponent + print("[\(fileName):\(line)] \(function): \(message)") + #endif + + logs.append("\(message)") + } +} + +// MARK: - Tunnel Manager +class TunnelManager: ObservableObject { + @Published var hasLocalDeviceSupport = false + @Published var tunnelStatus: TunnelStatus = .disconnected + + static var shared = TunnelManager() + + private var vpnManager: NETunnelProviderManager? + private var vpnObserver: NSObjectProtocol? + + private var tunnelDeviceIp: String { + UserDefaults.standard.string(forKey: "TunnelDeviceIP") ?? "10.7.0.0" + } + + private var tunnelFakeIp: String { + UserDefaults.standard.string(forKey: "TunnelFakeIP") ?? "10.7.0.1" + } + + private var tunnelSubnetMask: String { + UserDefaults.standard.string(forKey: "TunnelSubnetMask") ?? "255.255.255.0" + } + + private var tunnelBundleId: String { + Bundle.main.bundleIdentifier!.appending(".TunnelProv") + } + + enum TunnelStatus: String { + case disconnected = "Disconnected" + case connecting = "Connecting" + case connected = "Connected" + case disconnecting = "Disconnecting" + case error = "Error" + + var color: Color { + switch self { + case .disconnected: return .gray + case .connecting: return .orange + case .connected: return .green + case .disconnecting: return .orange + case .error: return .red + } + } + + var systemImage: String { + switch self { + case .disconnected: return "network.slash" + case .connecting: return "network.badge.shield.half.filled" + case .connected: return "checkmark.shield.fill" + case .disconnecting: return "network.badge.shield.half.filled" + case .error: return "exclamationmark.shield.fill" + } + } + } + + private init() { + loadTunnelPreferences() + setupStatusObserver() + } + + // MARK: - Private Methods + private func loadTunnelPreferences() { + NETunnelProviderManager.loadAllFromPreferences { [weak self] (managers, error) in + guard let self = self else { return } + + DispatchQueue.main.async { + + if let error = error { + VPNLogger.shared.log("Error loading preferences: \(error.localizedDescription)") + self.tunnelStatus = .error + return + } + + self.hasLocalDeviceSupport = true + + if let managers = managers, !managers.isEmpty { + for manager in managers { + if let proto = manager.protocolConfiguration as? NETunnelProviderProtocol, + proto.providerBundleIdentifier == self.tunnelBundleId { + self.vpnManager = manager + self.updateTunnelStatus(from: manager.connection.status) + VPNLogger.shared.log("Loaded existing tunnel configuration") + break + } + } + + // If we didn't find a matching manager, use the first one + if self.vpnManager == nil, let firstManager = managers.first { + self.vpnManager = firstManager + self.updateTunnelStatus(from: firstManager.connection.status) + VPNLogger.shared.log("Using existing tunnel configuration") + } + } else { + VPNLogger.shared.log("No existing tunnel configuration found") + } + } + } + } + + private func setupStatusObserver() { + NotificationCenter.default.addObserver( + forName: .NEVPNStatusDidChange, + object: nil, + queue: .main + ) { [weak self] notification in + guard let self = self, + let connection = notification.object as? NEVPNConnection else { + return + } + + self.updateTunnelStatus(from: connection.status) + } + } + + private func updateTunnelStatus(from connectionStatus: NEVPNStatus) { + DispatchQueue.main.async { + switch connectionStatus { + case .invalid, .disconnected: + self.tunnelStatus = .disconnected + case .connecting: + self.tunnelStatus = .connecting + case .connected: + self.tunnelStatus = .connected + case .disconnecting: + self.tunnelStatus = .disconnecting + case .reasserting: + self.tunnelStatus = .connecting + @unknown default: + self.tunnelStatus = .error + } + + VPNLogger.shared.log("VPN status updated: \(self.tunnelStatus.rawValue)") + } + } + + private func createOrUpdateTunnelConfiguration(completion: @escaping (Bool) -> Void) { + // First check if we already have configurations + NETunnelProviderManager.loadAllFromPreferences { [weak self] (managers, error) in + guard let self = self else { return completion(false) } + + if let error = error { + VPNLogger.shared.log("Error loading preferences: \(error.localizedDescription)") + return completion(false) + } + + let manager: NETunnelProviderManager + if let existingManagers = managers, !existingManagers.isEmpty { + if let matchingManager = existingManagers.first(where: { + ($0.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.tunnelBundleId + }) { + manager = matchingManager + VPNLogger.shared.log("Updating existing tunnel configuration") + } else { + manager = existingManagers[0] + VPNLogger.shared.log("Using first available tunnel configuration") + } + } else { + // Create a new manager if none exists + manager = NETunnelProviderManager() + VPNLogger.shared.log("Creating new tunnel configuration") + } + + manager.localizedDescription = "StosVPN" + + let proto = NETunnelProviderProtocol() + proto.providerBundleIdentifier = self.tunnelBundleId + proto.serverAddress = "StosVPN's Local Network Tunnel" + manager.protocolConfiguration = proto + + let onDemandRule = NEOnDemandRuleEvaluateConnection() + onDemandRule.interfaceTypeMatch = .any + onDemandRule.connectionRules = [NEEvaluateConnectionRule( + matchDomains: ["localhost"], + andAction: .connectIfNeeded + )] + + manager.onDemandRules = [onDemandRule] + manager.isOnDemandEnabled = true + manager.isEnabled = true + + manager.saveToPreferences { [weak self] error in + guard let self = self else { return completion(false) } + + DispatchQueue.main.async { + if let error = error { + VPNLogger.shared.log("Error saving tunnel configuration: \(error.localizedDescription)") + completion(false) + return + } + + self.vpnManager = manager + VPNLogger.shared.log("Tunnel configuration saved successfully") + completion(true) + } + } + } + } + + // MARK: - Public Methods + + func toggleVPNConnection() { + if tunnelStatus == .connected || tunnelStatus == .connecting { + stopVPN() + } else { + startVPN() + } + } + + func startVPN() { + if let manager = vpnManager { + startExistingVPN(manager: manager) + } else { + createOrUpdateTunnelConfiguration { [weak self] success in + guard let self = self, success else { return } + self.loadTunnelPreferences() + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + if let manager = self.vpnManager { + self.startExistingVPN(manager: manager) + } + } + } + } + } + + private func startExistingVPN(manager: NETunnelProviderManager) { + guard tunnelStatus != .connected else { + VPNLogger.shared.log("Network tunnel is already connected") + return + } + + tunnelStatus = .connecting + + let options: [String: NSObject] = [ + "TunnelDeviceIP": tunnelDeviceIp as NSObject, + "TunnelFakeIP": tunnelFakeIp as NSObject, + "TunnelSubnetMask": tunnelSubnetMask as NSObject + ] + + do { + try manager.connection.startVPNTunnel(options: options) + VPNLogger.shared.log("Network tunnel start initiated") + } catch { + tunnelStatus = .error + VPNLogger.shared.log("Failed to start tunnel: \(error.localizedDescription)") + } + } + + func stopVPN() { + guard let manager = vpnManager else { return } + + tunnelStatus = .disconnecting + manager.connection.stopVPNTunnel() + VPNLogger.shared.log("Network tunnel stop initiated") + } + + deinit { + if let observer = vpnObserver { + NotificationCenter.default.removeObserver(observer) + } + } +} + +// MARK: - Views + +struct ContentView: View { + @StateObject private var tunnelManager = TunnelManager.shared + @State private var showSettings = false + @State var tunnel = false + @AppStorage("autoConnect") private var autoConnect = false + + var body: some View { + NavigationStack { + VStack(spacing: 30) { + Spacer() + + StatusIndicatorView() + + ConnectionButton( + action: { + tunnelManager.tunnelStatus == .connected ? tunnelManager.stopVPN() : tunnelManager.startVPN() + } + ) + + Spacer() + + if tunnelManager.tunnelStatus == .connected { + ConnectionStatsView() + } + } + .padding() + .navigationTitle("StosVPN") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button { + showSettings = true + } label: { + Image(systemName: "gear") + .foregroundColor(.primary) + } + } + } + .onAppear() { + if tunnelManager.tunnelStatus != .connected && autoConnect { + tunnelManager.startVPN() + } + } + + .sheet(isPresented: $showSettings) { + SettingsView() + } + } + } +} + + +struct StatusIndicatorView: View { + @StateObject private var tunnelManager = TunnelManager.shared + @State private var animationAmount = 1.0 + @State private var isAnimating = false + + var body: some View { + VStack(spacing: 20) { + ZStack { + Circle() + .stroke(tunnelManager.tunnelStatus.color.opacity(0.2), lineWidth: 20) + .frame(width: 200, height: 200) + + Circle() + .stroke(tunnelManager.tunnelStatus.color, lineWidth: 10) + .frame(width: 200, height: 200) + .scaleEffect(animationAmount) + .opacity(2 - animationAmount) + .animation(isAnimating ? Animation.easeOut(duration: 1.5).repeatForever(autoreverses: false) : .default, value: animationAmount) + + VStack(spacing: 10) { + Image(systemName: tunnelManager.tunnelStatus.systemImage) + .font(.system(size: 50)) + .foregroundColor(tunnelManager.tunnelStatus.color) + + Text(tunnelManager.tunnelStatus.rawValue) + .font(.headline) + .foregroundColor(.primary) + } + } + .onAppear { + updateAnimation() + } + .onChange(of: tunnelManager.tunnelStatus) { _ in + updateAnimation() + } + + Text(tunnelManager.tunnelStatus == .connected ? "Tunnel active" : "Tunnel inactive") + .font(.subheadline) + .foregroundColor(tunnelManager.tunnelStatus == .connected ? .green : .secondary) + } + } + + private func updateAnimation() { + switch tunnelManager.tunnelStatus { + case .disconnecting: + isAnimating = false + withAnimation { + animationAmount = 1.0 + } + case .disconnected: + isAnimating = false + animationAmount = 1.0 + default: + isAnimating = true + animationAmount = 1.0 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + withAnimation { + animationAmount = 2.0 + } + } + } + } +} + + +struct ConnectionButton: View { + @StateObject private var tunnelManager = TunnelManager.shared + let action: () -> Void + + var body: some View { + Button(action: action) { + HStack { + Text(buttonText) + .font(.headline) + .fontWeight(.semibold) + + if tunnelManager.tunnelStatus == .connecting || tunnelManager.tunnelStatus == .disconnecting { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .padding(.leading, 5) + } + } + .frame(width: 200, height: 50) + .background(buttonBackground) + .foregroundColor(.white) + .clipShape(Capsule()) + .shadow(color: Color.black.opacity(0.15), radius: 10, x: 0, y: 5) + } + .disabled(tunnelManager.tunnelStatus == .connecting || tunnelManager.tunnelStatus == .disconnecting) + } + + private var buttonText: String { + if tunnelManager.tunnelStatus == .connected { + return "Disconnect" + } else if tunnelManager.tunnelStatus == .connecting { + return "Connecting..." + } else if tunnelManager.tunnelStatus == .disconnecting { + return "Disconnecting..." + } else { + return "Connect" + } + } + + private var buttonBackground: some View { + Group { + if tunnelManager.tunnelStatus == .connected { + LinearGradient( + gradient: Gradient(colors: [Color.red.opacity(0.8), Color.red]), + startPoint: .leading, + endPoint: .trailing + ) + } else { + LinearGradient( + gradient: Gradient(colors: [Color.blue.opacity(0.8), Color.blue]), + startPoint: .leading, + endPoint: .trailing + ) + } + } + } +} + +struct ConnectionStatsView: View { + @State private var time = 0 + let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() + + var body: some View { + VStack(spacing: 25) { + Text("Connection Details") + .font(.headline) + .foregroundColor(.primary) + + HStack(spacing: 30) { + StatItemView( + title: "Time Connected", + value: formattedTime, + icon: "clock.fill" + ) + + StatItemView( + title: "Status", + value: "Active", + icon: "checkmark.circle.fill" + ) + } + + HStack(spacing: 30) { + StatItemView( + title: "Network Interface", + value: "Local", + icon: "network" + ) + + StatItemView( + title: "Assigned IP", + value: "10.7.0.1", + icon: "number" + ) + } + } + .padding() + .background( + RoundedRectangle(cornerRadius: 20) + .fill(Color(UIColor.darkGray)) + .shadow(color: Color.black.opacity(0.05), radius: 10, x: 0, y: 5) + ) + .onReceive(timer) { _ in + time += 1 + } + } + + var formattedTime: String { + let minutes = (time / 60) % 60 + let hours = time / 3600 + let seconds = time % 60 + + if hours > 0 { + return String(format: "%02d:%02d:%02d", hours, minutes, seconds) + } else { + return String(format: "%02d:%02d", minutes, seconds) + } + } +} + +struct StatItemView: View { + let title: String + let value: String + let icon: String + + var body: some View { + VStack(alignment: .leading, spacing: 10) { + HStack { + Image(systemName: icon) + .foregroundColor(.blue) + + Text(title) + .font(.caption) + .foregroundColor(.secondary) + } + + Text(value) + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.primary) + } + .frame(maxWidth: .infinity, alignment: .leading) + } +} + +struct SettingsView: View { + @Environment(\.dismiss) private var dismiss + @AppStorage("TunnelDeviceIP") private var deviceIP = "10.7.0.0" + @AppStorage("TunnelFakeIP") private var fakeIP = "10.7.0.1" + @AppStorage("TunnelSubnetMask") private var subnetMask = "255.255.255.0" + @AppStorage("autoConnect") private var autoConnect = false + + var body: some View { + NavigationStack { + List { + Section(header: Text("Connection Settings")) { + Toggle("Auto-connect on Launch", isOn: $autoConnect) + + NavigationLink(destination: ConnectionLogView()) { + Label("Connection Logs", systemImage: "doc.text") + } + } + + Section(header: Text("Network Configuration")) { + HStack { + Text("Device IP") + Spacer() + TextField("Device IP", text: $deviceIP) + .multilineTextAlignment(.trailing) + .foregroundColor(.secondary) + .keyboardType(.numbersAndPunctuation) + } + + HStack { + Text("Tunnel IP") + Spacer() + TextField("Tunnel IP", text: $fakeIP) + .multilineTextAlignment(.trailing) + .foregroundColor(.secondary) + .keyboardType(.numbersAndPunctuation) + } + + HStack { + Text("Subnet Mask") + Spacer() + TextField("Subnet Mask", text: $subnetMask) + .multilineTextAlignment(.trailing) + .foregroundColor(.secondary) + .keyboardType(.numbersAndPunctuation) + } + } + + Section(header: Text("About")) { + HStack { + Text("App Version") + Spacer() + Text("1.0.0") + .foregroundColor(.secondary) + } + + NavigationLink(destination: PrivacyPolicyView()) { + Text("Privacy Policy") + } + + NavigationLink(destination: HelpView()) { + Text("Help & Support") + } + } + } + .navigationTitle("Settings") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button("Done") { + dismiss() + } + } + } + } + } +} + + +struct ConnectionLogView: View { + @StateObject var logger = VPNLogger.shared + var body: some View { + List(logger.logs, id: \.self) { log in + Text(log) + .font(.system(.body, design: .monospaced)) + } + .navigationTitle("Logs") + .navigationBarTitleDisplayMode(.inline) + } +} + +struct PrivacyPolicyView: View { + var body: some View { + ScrollView { + VStack(alignment: .leading, spacing: 15) { + Text("Privacy Policy") + .font(.title) + .fontWeight(.bold) + .padding(.bottom, 10) + + Text("Your privacy is important to us. This application is designed to create a local network interface for development and testing purposes.") + + Text("Data Collection") + .font(.headline) + .padding(.top, 10) + + Text("This application does not collect any personal information. All traffic remains on your device.") + + Text("Permissions") + .font(.headline) + .padding(.top, 10) + + Text("This app requires network extension permissions to create a virtual network interface on your device.") + } + .padding() + } + .navigationTitle("Privacy Policy") + .navigationBarTitleDisplayMode(.inline) + } +} + +struct HelpView: View { + var body: some View { + List { + Section(header: Text("Frequently Asked Questions")) { + NavigationLink("What does this app do?") { + VStack(alignment: .leading, spacing: 15) { + Text("This app creates a local network interface that can be used for development and testing purposes.") + .padding(.bottom, 10) + + Text("Common use cases include:") + .fontWeight(.medium) + + Text("• Testing applications that require specific network configurations") + Text("• Development of network-related features") + Text("• Isolating network traffic for analysis or debugging") + } + .padding() + } + + NavigationLink("Why does the connection fail?") { + + Text("Why does the connection fail?") + .fontWeight(.medium) + + Text("Connection failures could be due to system permission issues, configuration errors, or iOS restrictions. Try restarting the app or checking your settings.") + .padding() + } + + NavigationLink("What is this app for?") { + + Text("What is this app for?") + .fontWeight(.medium) + + Text("This app is for connecting to local Servers on iOS devices to debug or test specific network applications, such as web-servers, or other network-related applications.") + .padding() + } + } + + Section(header: Text("App Information")) { + HStack { + Image(systemName: "exclamationmark.shield") + Text("Requires iOS 16.0 or later") + } + + HStack { + Image(systemName: "lock.shield") + Text("Uses Apple's Network Extension API") + } + } + } + .navigationTitle("Help & Support") + .navigationBarTitleDisplayMode(.inline) + } +} + +#Preview { + ContentView() +} diff --git a/StosVPN/Info.plist b/StosVPN/Info.plist new file mode 100644 index 0000000..4f62b60 --- /dev/null +++ b/StosVPN/Info.plist @@ -0,0 +1,10 @@ + + + + + NSBonjourServices + + _apple-mobdev2._tcp + + + diff --git a/StosVPN/Preview Content/Preview Assets.xcassets/Contents.json b/StosVPN/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/StosVPN/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StosVPN/StosVPN-Bridging-Header.h b/StosVPN/StosVPN-Bridging-Header.h new file mode 100644 index 0000000..1b2cb5d --- /dev/null +++ b/StosVPN/StosVPN-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/StosVPN/StosVPN.entitlements b/StosVPN/StosVPN.entitlements new file mode 100644 index 0000000..dd735cf --- /dev/null +++ b/StosVPN/StosVPN.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.developer.networking.vpn.api + + allow-vpn + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + + diff --git a/StosVPN/StosVPNApp.swift b/StosVPN/StosVPNApp.swift new file mode 100644 index 0000000..507cc9c --- /dev/null +++ b/StosVPN/StosVPNApp.swift @@ -0,0 +1,17 @@ +// +// StosVPNApp.swift +// StosVPN +// +// Created by Stossy11 on 28/03/2025. +// + +import SwiftUI + +@main +struct StosVPNApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/TunnelProv/Info.plist b/TunnelProv/Info.plist new file mode 100644 index 0000000..3059459 --- /dev/null +++ b/TunnelProv/Info.plist @@ -0,0 +1,13 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.networkextension.packet-tunnel + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).PacketTunnelProvider + + + diff --git a/TunnelProv/PacketTunnelProvider.swift b/TunnelProv/PacketTunnelProvider.swift new file mode 100644 index 0000000..afdd5f5 --- /dev/null +++ b/TunnelProv/PacketTunnelProvider.swift @@ -0,0 +1,156 @@ +// +// PacketTunnelProvider.swift +// TunnelProv +// +// Created by Stossy11 on 28/03/2025. +// + +import NetworkExtension + +class PacketTunnelProvider: NEPacketTunnelProvider { + var tunnelDeviceIp: String = "10.7.0.0" + var tunnelFakeIp: String = "10.7.0.1" + var tunnelSubnetMask: String = "255.255.255.0" + + override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { + if let deviceIp = options?["TunnelDeviceIP"] as? String { + tunnelDeviceIp = deviceIp + } + if let fakeIp = options?["TunnelFakeIP"] as? String { + tunnelFakeIp = fakeIp + } + + let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: tunnelDeviceIp) + let ipv4 = NEIPv4Settings(addresses: [tunnelDeviceIp], subnetMasks: [tunnelSubnetMask]) + ipv4.includedRoutes = [NEIPv4Route(destinationAddress: tunnelDeviceIp, subnetMask: tunnelSubnetMask)] + ipv4.excludedRoutes = [.default()] + settings.ipv4Settings = ipv4 + setTunnelNetworkSettings(settings) { error in + if error == nil { + self.readPackets() + } + completionHandler(error) + } + } + + override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { + // Add code here to start the process of stopping the tunnel. + completionHandler() + } + + override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { + // Add code here to handle the message. + if let handler = completionHandler { + handler(messageData) + } + } + + override func sleep(completionHandler: @escaping () -> Void) { + completionHandler() + } + + override func wake() { + // Add code here to wake up. + } + + private func readPackets() { + packetFlow.readPackets { packets, protocols in + var output: [Data] = [] + for (i, packet) in packets.enumerated() { + var modifiedPacket = packet + if protocols[i].int32Value == AF_INET { + modifiedPacket = self.packetReplaceIp(packet, self.tunnelDeviceIp, self.tunnelFakeIp, self.tunnelFakeIp, self.tunnelDeviceIp) + } + + NSLog("wow \(modifiedPacket)") + + // Ensure packet has at least 20 bytes before swapping + if modifiedPacket.count >= 20 { + var mutableBytes = [UInt8](modifiedPacket) + + // Swap bytes + (mutableBytes[12], mutableBytes[16]) = (mutableBytes[16], mutableBytes[12]) + (mutableBytes[13], mutableBytes[17]) = (mutableBytes[17], mutableBytes[13]) + (mutableBytes[14], mutableBytes[18]) = (mutableBytes[18], mutableBytes[14]) + (mutableBytes[15], mutableBytes[19]) = (mutableBytes[19], mutableBytes[15]) + + modifiedPacket = Data(mutableBytes) + } + + output.append(modifiedPacket) + } + self.packetFlow.writePackets(output, withProtocols: protocols) + self.readPackets() + } + } + + + private func packetReplaceIp(_ data: Data, _ sourceSearch: String, _ sourceReplace: String, _ destSearch: String, _ destReplace: String) -> Data { + // Check if packet is too small for IPv4 header + if data.count < 20 { + return data + } + + // Convert IP strings to Data with network byte order (big-endian) + func ipToUInt32(_ ipString: String) -> UInt32 { + let components = ipString.split(separator: ".") + var result: UInt32 = 0 + + if components.count == 4, + let byte1 = UInt32(components[0]), + let byte2 = UInt32(components[1]), + let byte3 = UInt32(components[2]), + let byte4 = UInt32(components[3]) { + result = (byte1 << 24) | (byte2 << 16) | (byte3 << 8) | byte4 + } + + return result + } + + // Convert IP strings to UInt32 + let sourceSearchIP = ipToUInt32(sourceSearch) + let sourceReplaceIP = ipToUInt32(sourceReplace) + let destSearchIP = ipToUInt32(destSearch) + let destReplaceIP = ipToUInt32(destReplace) + + // Extract source and destination IPs from packet + var sourcePacketIP: UInt32 = 0 + var destPacketIP: UInt32 = 0 + + (data as NSData).getBytes(&sourcePacketIP, range: NSRange(location: 12, length: 4)) + (data as NSData).getBytes(&destPacketIP, range: NSRange(location: 16, length: 4)) + + if sourceSearchIP != sourcePacketIP && destSearchIP != destPacketIP { + return data + } + + let mutableData = NSMutableData(data: data) + + if sourceSearchIP == sourcePacketIP { + var sourceIP = sourceReplaceIP + mutableData.replaceBytes(in: NSRange(location: 12, length: 4), withBytes: &sourceIP) + } + + if destSearchIP == destPacketIP { + var destIP = destReplaceIP + mutableData.replaceBytes(in: NSRange(location: 16, length: 4), withBytes: &destIP) + } + + return mutableData as Data + } + + // Helper function to convert IP string to Data + private func ipToData(_ ip: String) -> Data { + let components = ip.split(separator: ".") + var data = Data(capacity: 4) + + for component in components { + if let byte = UInt8(component) { + data.append(byte) + } + } + + return data + } +} + diff --git a/TunnelProv/TunnelProv.entitlements b/TunnelProv/TunnelProv.entitlements new file mode 100644 index 0000000..ffab33e --- /dev/null +++ b/TunnelProv/TunnelProv.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + +