mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-15 20:10:30 +00:00
Debug pt 2
This commit is contained in:
parent
50b77252b1
commit
8bcd12f4bc
8 changed files with 763 additions and 114 deletions
|
|
@ -6,6 +6,11 @@
|
|||
objectVersion = 77;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
DCA690122DAF660E007C91A8 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCA690112DAF660E007C91A8 /* NetworkExtension.framework */; };
|
||||
DCA6901A2DAF660E007C91A8 /* TunnelProv.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DCA690102DAF660E007C91A8 /* TunnelProv.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
DC6F1D492D94EADF0071B2B6 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
|
|
@ -21,13 +26,36 @@
|
|||
remoteGlobalIDString = DC6F1D362D94EADD0071B2B6;
|
||||
remoteInfo = StikJIT;
|
||||
};
|
||||
DCA690182DAF660E007C91A8 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = DC6F1D2F2D94EADD0071B2B6 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = DCA6900F2DAF660E007C91A8;
|
||||
remoteInfo = TunnelProv;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
DCA6901F2DAF660E007C91A8 /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
DCA6901A2DAF660E007C91A8 /* TunnelProv.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
DC5716242D9564760099302E /* em_proxy.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = em_proxy.xcframework; sourceTree = "<group>"; };
|
||||
DC6F1D372D94EADD0071B2B6 /* StikDebug.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StikDebug.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DC6F1D482D94EADF0071B2B6 /* StikDebugTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StikDebugTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DC6F1D522D94EADF0071B2B6 /* StikDebugUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StikDebugUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DCA690102DAF660E007C91A8 /* TunnelProv.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TunnelProv.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DCA690112DAF660E007C91A8 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
|
@ -38,6 +66,13 @@
|
|||
);
|
||||
target = DC6F1D362D94EADD0071B2B6 /* StikDebug */;
|
||||
};
|
||||
DCA6901E2DAF660E007C91A8 /* Exceptions for "TunnelProv" folder in "TunnelProv" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
);
|
||||
target = DCA6900F2DAF660E007C91A8 /* TunnelProv */;
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
|
|
@ -59,6 +94,14 @@
|
|||
path = StikJITUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DCA690132DAF660E007C91A8 /* TunnelProv */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
DCA6901E2DAF660E007C91A8 /* Exceptions for "TunnelProv" folder in "TunnelProv" target */,
|
||||
);
|
||||
path = TunnelProv;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -83,6 +126,14 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DCA6900D2DAF660E007C91A8 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DCA690122DAF660E007C91A8 /* NetworkExtension.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
|
|
@ -92,6 +143,7 @@
|
|||
DC6F1D392D94EADD0071B2B6 /* StikJIT */,
|
||||
DC6F1D4B2D94EADF0071B2B6 /* StikJITTests */,
|
||||
DC6F1D552D94EADF0071B2B6 /* StikJITUITests */,
|
||||
DCA690132DAF660E007C91A8 /* TunnelProv */,
|
||||
DC6F1D752D94EB620071B2B6 /* Frameworks */,
|
||||
DC6F1D382D94EADD0071B2B6 /* Products */,
|
||||
);
|
||||
|
|
@ -103,6 +155,7 @@
|
|||
DC6F1D372D94EADD0071B2B6 /* StikDebug.app */,
|
||||
DC6F1D482D94EADF0071B2B6 /* StikDebugTests.xctest */,
|
||||
DC6F1D522D94EADF0071B2B6 /* StikDebugUITests.xctest */,
|
||||
DCA690102DAF660E007C91A8 /* TunnelProv.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -111,6 +164,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
DC5716242D9564760099302E /* em_proxy.xcframework */,
|
||||
DCA690112DAF660E007C91A8 /* NetworkExtension.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -125,10 +179,12 @@
|
|||
DC6F1D332D94EADD0071B2B6 /* Sources */,
|
||||
DC6F1D342D94EADD0071B2B6 /* Frameworks */,
|
||||
DC6F1D352D94EADD0071B2B6 /* Resources */,
|
||||
DCA6901F2DAF660E007C91A8 /* Embed Foundation Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
DCA690192DAF660E007C91A8 /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
DC6F1D392D94EADD0071B2B6 /* StikJIT */,
|
||||
|
|
@ -186,6 +242,28 @@
|
|||
productReference = DC6F1D522D94EADF0071B2B6 /* StikDebugUITests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.ui-testing";
|
||||
};
|
||||
DCA6900F2DAF660E007C91A8 /* TunnelProv */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DCA6901B2DAF660E007C91A8 /* Build configuration list for PBXNativeTarget "TunnelProv" */;
|
||||
buildPhases = (
|
||||
DCA6900C2DAF660E007C91A8 /* Sources */,
|
||||
DCA6900D2DAF660E007C91A8 /* Frameworks */,
|
||||
DCA6900E2DAF660E007C91A8 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
DCA690132DAF660E007C91A8 /* TunnelProv */,
|
||||
);
|
||||
name = TunnelProv;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = TunnelProv;
|
||||
productReference = DCA690102DAF660E007C91A8 /* TunnelProv.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
|
|
@ -208,6 +286,9 @@
|
|||
CreatedOnToolsVersion = 16.2;
|
||||
TestTargetID = DC6F1D362D94EADD0071B2B6;
|
||||
};
|
||||
DCA6900F2DAF660E007C91A8 = {
|
||||
CreatedOnToolsVersion = 16.2;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = DC6F1D322D94EADD0071B2B6 /* Build configuration list for PBXProject "StikDebug" */;
|
||||
|
|
@ -227,6 +308,7 @@
|
|||
DC6F1D362D94EADD0071B2B6 /* StikDebug */,
|
||||
DC6F1D472D94EADF0071B2B6 /* StikDebugTests */,
|
||||
DC6F1D512D94EADF0071B2B6 /* StikDebugUITests */,
|
||||
DCA6900F2DAF660E007C91A8 /* TunnelProv */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
|
@ -253,6 +335,13 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DCA6900E2DAF660E007C91A8 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
|
|
@ -277,6 +366,13 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DCA6900C2DAF660E007C91A8 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
|
|
@ -290,6 +386,11 @@
|
|||
target = DC6F1D362D94EADD0071B2B6 /* StikDebug */;
|
||||
targetProxy = DC6F1D532D94EADF0071B2B6 /* PBXContainerItemProxy */;
|
||||
};
|
||||
DCA690192DAF660E007C91A8 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = DCA6900F2DAF660E007C91A8 /* TunnelProv */;
|
||||
targetProxy = DCA690182DAF660E007C91A8 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
|
|
@ -592,6 +693,63 @@
|
|||
};
|
||||
name = Release;
|
||||
};
|
||||
DCA6901C2DAF660E007C91A8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = TunnelProv/TunnelProv.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = SZ977XLF24;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = TunnelProv/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = TunnelProv;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stik.sj.TunnelProv;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
DCA6901D2DAF660E007C91A8 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = TunnelProv/TunnelProv.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = SZ977XLF24;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = TunnelProv/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = TunnelProv;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stik.sj.TunnelProv;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
|
|
@ -631,6 +789,15 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
DCA6901B2DAF660E007C91A8 /* Build configuration list for PBXNativeTarget "TunnelProv" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
DCA6901C2DAF660E007C91A8 /* Debug */,
|
||||
DCA6901D2DAF660E007C91A8 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = DC6F1D2F2D94EADD0071B2B6 /* Project object */;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.developer.networking.vpn.api</key>
|
||||
<string>allow-vpn</string>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,268 @@
|
|||
import SwiftUI
|
||||
import Network
|
||||
import UniformTypeIdentifiers
|
||||
import NetworkExtension
|
||||
|
||||
// MARK: - Welcome Sheet
|
||||
|
||||
struct WelcomeSheetView: View {
|
||||
// A callback to dismiss the sheet and trigger VPN setup.
|
||||
var onDismiss: (() -> Void)?
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 24) {
|
||||
Text("Welcome!")
|
||||
.font(.system(.largeTitle, design: .rounded).weight(.bold))
|
||||
.foregroundColor(.primary)
|
||||
.padding(.top)
|
||||
|
||||
Text("Thanks for installing the app. This brief introduction will help you get started.")
|
||||
.font(.body)
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
|
||||
Text("StikDebug is an on-device debugger designed specifically for self-developed apps. It helps streamline testing and troubleshooting without sending any data to external servers.")
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
|
||||
Text("The next step will prompt you to allow VPN permissions. This is necessary for the app to function properly. The VPN configuration allows your device to securely connect to itself — nothing more. Rest assured, no data is collected or sent externally. Everything stays on your device.")
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
|
||||
Text("You might encounter a -17 error after your initial launch. Simply tap 'OK' and the issue will be resolved once a valid pairing file has been imported.")
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
|
||||
|
||||
Button(action: {
|
||||
onDismiss?()
|
||||
}) {
|
||||
Text("Continue")
|
||||
.font(.headline)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(RoundedRectangle(cornerRadius: 12).fill(Color.accentColor))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom)
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 30, style: .continuous)
|
||||
.fill(Color(UIColor.secondarySystemBackground))
|
||||
.shadow(radius: 20)
|
||||
)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - VPN Logger
|
||||
|
||||
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 tunnelStatus: TunnelStatus = .disconnected
|
||||
static var shared = TunnelManager()
|
||||
|
||||
private var vpnManager: NETunnelProviderManager?
|
||||
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"
|
||||
}
|
||||
|
||||
private init() {
|
||||
loadTunnelPreferences()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(statusDidChange(_:)), name: .NEVPNStatusDidChange, object: nil)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func statusDidChange(_ notification: Notification) {
|
||||
if let connection = notification.object as? NEVPNConnection {
|
||||
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) {
|
||||
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 {
|
||||
manager = NETunnelProviderManager()
|
||||
VPNLogger.shared.log("Creating new tunnel configuration")
|
||||
}
|
||||
|
||||
manager.localizedDescription = "StikDebug"
|
||||
let proto = NETunnelProviderProtocol()
|
||||
proto.providerBundleIdentifier = self.tunnelBundleId
|
||||
proto.serverAddress = "StikDebug's Local Network Tunnel"
|
||||
manager.protocolConfiguration = proto
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AccentColor Environment Key
|
||||
|
||||
struct AccentColorKey: EnvironmentKey {
|
||||
static let defaultValue: Color = .blue
|
||||
|
|
@ -20,6 +282,8 @@ extension EnvironmentValues {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Helper Functions and Globals
|
||||
|
||||
let fileManager = FileManager.default
|
||||
|
||||
func httpGet(_ urlString: String, result: @escaping (String?) -> Void) {
|
||||
|
|
@ -63,6 +327,8 @@ func UpdateRetrieval() -> Bool {
|
|||
return res
|
||||
}
|
||||
|
||||
// MARK: - DNS Checker
|
||||
|
||||
class DNSChecker: ObservableObject {
|
||||
@Published var appleIP: String?
|
||||
@Published var controlIP: String?
|
||||
|
|
@ -168,8 +434,15 @@ class DNSChecker: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Main App
|
||||
|
||||
// Global state variable for the heartbeat response.
|
||||
var pubHeartBeat = false
|
||||
|
||||
@main
|
||||
struct HeartbeatApp: App {
|
||||
@AppStorage("hasLaunchedBefore") var hasLaunchedBefore: Bool = false
|
||||
@State private var showWelcomeSheet: Bool = false
|
||||
@State private var isLoading2 = true
|
||||
@State private var isPairing = false
|
||||
@State private var heartBeat = false
|
||||
|
|
@ -249,109 +522,132 @@ struct HeartbeatApp: App {
|
|||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
if isLoading2 {
|
||||
// Pass bindings to LoadingView for alert handling and version check.
|
||||
LoadingView(showAlert: $show_alert, alertTitle: $alert_title, alertMessage: $alert_string)
|
||||
.onAppear {
|
||||
dnsChecker.checkDNS()
|
||||
|
||||
startProxy() { result, error in
|
||||
if result {
|
||||
checkVPNConnection() { result, vpn_error in
|
||||
if result {
|
||||
if FileManager.default.fileExists(atPath: URL.documentsDirectory.appendingPathComponent("pairingFile.plist").path) {
|
||||
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
|
||||
if pubHeartBeat {
|
||||
isLoading2 = false
|
||||
timer.invalidate()
|
||||
} else {
|
||||
if let error {
|
||||
if error == -9 { // InvalidHostID is -9
|
||||
isPairing = true
|
||||
} else {
|
||||
startHeartbeatInBackground()
|
||||
Group {
|
||||
if isLoading2 {
|
||||
LoadingView(showAlert: $show_alert, alertTitle: $alert_title, alertMessage: $alert_string)
|
||||
.onAppear {
|
||||
dnsChecker.checkDNS()
|
||||
|
||||
startProxy() { result, error in
|
||||
if result {
|
||||
checkVPNConnection() { result, vpn_error in
|
||||
if result {
|
||||
if FileManager.default.fileExists(atPath: URL.documentsDirectory.appendingPathComponent("pairingFile.plist").path) {
|
||||
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
|
||||
if pubHeartBeat {
|
||||
isLoading2 = false
|
||||
timer.invalidate()
|
||||
} else {
|
||||
if let error {
|
||||
if error == -9 { // InvalidHostID is -9
|
||||
isPairing = true
|
||||
} else {
|
||||
startHeartbeatInBackground()
|
||||
}
|
||||
self.error = nil
|
||||
}
|
||||
self.error = nil
|
||||
}
|
||||
}
|
||||
startHeartbeatInBackground()
|
||||
} else {
|
||||
isLoading2 = false
|
||||
}
|
||||
} else if let vpn_error {
|
||||
showAlert(title: "Error", message: "EM Proxy failed to connect: \(vpn_error)", showOk: true) { _ in
|
||||
exit(0)
|
||||
}
|
||||
|
||||
startHeartbeatInBackground()
|
||||
} else {
|
||||
isLoading2 = false
|
||||
}
|
||||
} else if let vpn_error {
|
||||
showAlert(title: "Error", message: "EM Proxy failed to connect: \(vpn_error)", showOk: true) { _ in
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
} else if let error {
|
||||
showAlert(title: "Error", message: "EM Proxy Failed to start \(error)", showOk: true) { _ in }
|
||||
}
|
||||
} else if let error {
|
||||
showAlert(title: "Error", message: "EM Proxy Failed to start \(error)", showOk: true) { _ in }
|
||||
}
|
||||
}
|
||||
}
|
||||
.fileImporter(isPresented: $isPairing, allowedContentTypes: [UTType(filenameExtension: "mobiledevicepairing", conformingTo: .data)!, .propertyList]) { result in
|
||||
switch result {
|
||||
case .success(let url):
|
||||
.fileImporter(
|
||||
isPresented: $isPairing,
|
||||
allowedContentTypes: [
|
||||
UTType(filenameExtension: "mobiledevicepairing", conformingTo: .data)!,
|
||||
.propertyList
|
||||
]
|
||||
) { result in
|
||||
switch result {
|
||||
case .success(let url):
|
||||
let fileManager = FileManager.default
|
||||
let accessing = url.startAccessingSecurityScopedResource()
|
||||
|
||||
if fileManager.fileExists(atPath: url.path) {
|
||||
do {
|
||||
if fileManager.fileExists(atPath: URL.documentsDirectory.appendingPathComponent("pairingFile.plist").path) {
|
||||
try fileManager.removeItem(at: URL.documentsDirectory.appendingPathComponent("pairingFile.plist"))
|
||||
}
|
||||
try fileManager.copyItem(at: url, to: URL.documentsDirectory.appendingPathComponent("pairingFile.plist"))
|
||||
print("File copied successfully!")
|
||||
startHeartbeatInBackground()
|
||||
} catch {
|
||||
print("Error copying file: \(error)")
|
||||
}
|
||||
} else {
|
||||
print("Source file does not exist.")
|
||||
}
|
||||
|
||||
if accessing {
|
||||
url.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
case .failure(_):
|
||||
print("Failed")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MainTabView()
|
||||
.onAppear {
|
||||
applyTheme()
|
||||
let fileManager = FileManager.default
|
||||
let accessing = url.startAccessingSecurityScopedResource()
|
||||
|
||||
if fileManager.fileExists(atPath: url.path) {
|
||||
do {
|
||||
if fileManager.fileExists(atPath: URL.documentsDirectory.appendingPathComponent("pairingFile.plist").path) {
|
||||
try fileManager.removeItem(at: URL.documentsDirectory.appendingPathComponent("pairingFile.plist"))
|
||||
}
|
||||
try fileManager.copyItem(at: url, to: URL.documentsDirectory.appendingPathComponent("pairingFile.plist"))
|
||||
print("File copied successfully!")
|
||||
startHeartbeatInBackground()
|
||||
} catch {
|
||||
print("Error copying file: \(error)")
|
||||
}
|
||||
} else {
|
||||
print("Source file does not exist.")
|
||||
}
|
||||
|
||||
if accessing {
|
||||
url.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
case .failure(_):
|
||||
print("Failed")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MainTabView()
|
||||
.onAppear {
|
||||
applyTheme()
|
||||
let fileManager = FileManager.default
|
||||
for (index, urlString) in urls.enumerated() {
|
||||
let destinationURL = URL.documentsDirectory.appendingPathComponent(outputFiles[index])
|
||||
if !fileManager.fileExists(atPath: destinationURL.path) {
|
||||
downloadFile(from: urlString, to: destinationURL) { result in
|
||||
if (result != "") {
|
||||
alert_title = "An Error has Occurred"
|
||||
alert_string = "[Download DDI Error]: " + result
|
||||
show_alert = true
|
||||
for (index, urlString) in urls.enumerated() {
|
||||
let destinationURL = URL.documentsDirectory.appendingPathComponent(outputFiles[index])
|
||||
if !fileManager.fileExists(atPath: destinationURL.path) {
|
||||
downloadFile(from: urlString, to: destinationURL) { result in
|
||||
if (result != "") {
|
||||
alert_title = "An Error has Occurred"
|
||||
alert_string = "[Download DDI Error]: " + result
|
||||
show_alert = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.overlay(
|
||||
ZStack {
|
||||
if show_alert {
|
||||
CustomErrorView(
|
||||
title: alert_title,
|
||||
message: alert_string,
|
||||
onDismiss: {
|
||||
show_alert = false
|
||||
},
|
||||
showButton: true,
|
||||
primaryButtonText: "OK"
|
||||
)
|
||||
.overlay(
|
||||
ZStack {
|
||||
if show_alert {
|
||||
CustomErrorView(
|
||||
title: alert_title,
|
||||
message: alert_string,
|
||||
onDismiss: {
|
||||
show_alert = false
|
||||
},
|
||||
showButton: true,
|
||||
primaryButtonText: "OK"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
// On first launch, present the welcome sheet.
|
||||
// Otherwise, start the VPN automatically.
|
||||
if !hasLaunchedBefore {
|
||||
showWelcomeSheet = true
|
||||
} else {
|
||||
TunnelManager.shared.startVPN()
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showWelcomeSheet) {
|
||||
WelcomeSheetView {
|
||||
// When the user taps "Continue", mark the app as launched and start the VPN.
|
||||
hasLaunchedBefore = true
|
||||
showWelcomeSheet = false
|
||||
TunnelManager.shared.startVPN()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase) { newPhase in
|
||||
|
|
@ -436,7 +732,7 @@ struct HeartbeatApp: App {
|
|||
}
|
||||
}
|
||||
|
||||
var pubHeartBeat = false
|
||||
// MARK: - Additional Helpers
|
||||
|
||||
actor FunctionGuard<T> {
|
||||
private var runningTask: Task<T, Never>?
|
||||
|
|
@ -479,7 +775,6 @@ class MountingProgress: ObservableObject {
|
|||
|
||||
private func mount() {
|
||||
self.coolisMounted = isMounted()
|
||||
let fileManager = FileManager.default
|
||||
let pairingpath = URL.documentsDirectory.appendingPathComponent("pairingFile.plist").path
|
||||
|
||||
if isPairing(), !isMounted() {
|
||||
|
|
@ -544,8 +839,7 @@ func startHeartbeatInBackground() {
|
|||
print("Error: \(message ?? "") (Code: \(result))")
|
||||
DispatchQueue.main.async {
|
||||
// Special handling for InvalidHostID error (code -9)
|
||||
if result == -9 { // InvalidHostID is -9
|
||||
// Delete the invalid pairing file
|
||||
if result == -9 {
|
||||
do {
|
||||
try FileManager.default.removeItem(at: URL.documentsDirectory.appendingPathComponent("pairingFile.plist"))
|
||||
print("Removed invalid pairing file")
|
||||
|
|
@ -553,7 +847,6 @@ func startHeartbeatInBackground() {
|
|||
print("Error removing invalid pairing file: \(error)")
|
||||
}
|
||||
|
||||
// Show alert with option to select a new pairing file
|
||||
showAlert(
|
||||
title: "Invalid Pairing File",
|
||||
message: "The pairing file is invalid or expired. Please select a new pairing file.",
|
||||
|
|
@ -561,11 +854,9 @@ func startHeartbeatInBackground() {
|
|||
showTryAgain: false,
|
||||
primaryButtonText: "Select New File"
|
||||
) { _ in
|
||||
// This will be handled by the HomeView's fileImporter
|
||||
NotificationCenter.default.post(name: NSNotification.Name("ShowPairingFilePicker"), object: nil)
|
||||
}
|
||||
} else {
|
||||
// Handle other errors as before
|
||||
showAlert(
|
||||
title: "Heartbeat Error",
|
||||
message: "Failed to connect to Heartbeat (\(result))",
|
||||
|
|
@ -645,16 +936,14 @@ struct LoadingView: View {
|
|||
.shadow(color: accentColor.opacity(0.4), radius: 10, x: 0, y: 0)
|
||||
.onAppear {
|
||||
animate = true
|
||||
|
||||
let os = ProcessInfo.processInfo.operatingSystemVersion
|
||||
if os.majorVersion < 17 || (os.majorVersion == 17 && os.minorVersion < 4) {
|
||||
// Show alert for unsupported host iOS version
|
||||
alertTitle = "Unsupported OS Version"
|
||||
alertMessage = "StikJIT only supports 17.4 and above. Your device is running iOS/iPadOS \(os.majorVersion).\(os.minorVersion).\(os.patchVersion)"
|
||||
showAlert = true
|
||||
}
|
||||
}
|
||||
|
||||
let os = ProcessInfo.processInfo.operatingSystemVersion
|
||||
if os.majorVersion < 17 || (os.majorVersion == 17 && os.minorVersion < 4) {
|
||||
alertTitle = "Unsupported OS Version"
|
||||
alertMessage = "StikJIT only supports 17.4 and above. Your device is running iOS/iPadOS \(os.majorVersion).\(os.minorVersion).\(os.patchVersion)"
|
||||
showAlert = true
|
||||
}
|
||||
}
|
||||
|
||||
Text("Loading...")
|
||||
.font(.system(size: 20, weight: .medium, design: .rounded))
|
||||
.foregroundColor(isDarkMode ? .white.opacity(0.8) : .black.opacity(0.8))
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ struct HomeView: View {
|
|||
}
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: pairingFileExists ? "bolt.fill" : "doc.badge.plus")
|
||||
Image(systemName: pairingFileExists ? "cable.connector.horizontal" : "doc.badge.plus")
|
||||
.font(.system(size: 20))
|
||||
Text(pairingFileExists ? "Connect" : "Select Pairing File")
|
||||
.font(.system(.title3, design: .rounded))
|
||||
|
|
|
|||
|
|
@ -358,10 +358,8 @@ struct SettingsView: View {
|
|||
// Vertical stack of collaborators
|
||||
VStack(spacing: 12) {
|
||||
|
||||
CollaboratorRow(name: "Stossy11", url: "https://github.com/Stossy11", imageUrl: developerProfiles["Stossy11"] ?? "", quote: "I helped I guess :3")
|
||||
|
||||
CollaboratorRow(name: "Neo", url: "https://neoarz.xyz/", imageUrl: developerProfiles["Neo"] ?? "", quote: "Chicken Jockey")
|
||||
|
||||
CollaboratorRow(name: "Stossy11", url: "https://github.com/Stossy11", imageUrl: developerProfiles["Stossy11"] ?? "")
|
||||
CollaboratorRow(name: "Neo", url: "https://neoarz.xyz/", imageUrl: developerProfiles["Neo"] ?? "")
|
||||
CollaboratorRow(name: "Se2crid", url: "https://github.com/Se2crid", imageUrl: developerProfiles["Se2crid"] ?? "")
|
||||
|
||||
CollaboratorRow(name: "Huge_Black", url: "https://github.com/HugeBlack", imageUrl: developerProfiles["Huge_Black"] ?? "")
|
||||
|
|
|
|||
13
TunnelProv/Info.plist
Normal file
13
TunnelProv/Info.plist
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.networkextension.packet-tunnel</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
162
TunnelProv/PacketTunnelProvider.swift
Normal file
162
TunnelProv/PacketTunnelProvider.swift
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
//
|
||||
// PacketTunnelProvider.swift
|
||||
// TunnelProv
|
||||
//
|
||||
// Created by Stephen on 4/16/25.
|
||||
//
|
||||
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
14
TunnelProv/TunnelProv.entitlements
Normal file
14
TunnelProv/TunnelProv.entitlements
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.stik.sj</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
Loading…
Add table
Add a link
Reference in a new issue