feat: embed loopback packet tunnel extension

This commit is contained in:
zy860 2026-08-18 14:36:18 +08:00
parent 5fd770c936
commit 2272aa1331
4 changed files with 257 additions and 0 deletions

View file

@ -9,6 +9,10 @@
/* Begin PBXBuildFile section */
68D569BE2E1B415700A5BA36 /* CodeEditorView in Frameworks */ = {isa = PBXBuildFile; productRef = 68D569BD2E1B415700A5BA36 /* CodeEditorView */; };
68D569C02E1B415700A5BA36 /* LanguageSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 68D569BF2E1B415700A5BA36 /* LanguageSupport */; };
A1B2C3D40123456789ABC001 /* StikDebugTunnelConfiguration.swift in StikDebugTunnel Sources */ = {isa = PBXBuildFile; fileRef = A3B2C3D40123456789ABC003 /* StikDebugTunnelConfiguration.swift */; };
A1B2C3D40123456789ABC002 /* IPv4PacketRewriter.swift in StikDebugTunnel Sources */ = {isa = PBXBuildFile; fileRef = A3B2C3D40123456789ABC004 /* IPv4PacketRewriter.swift */; };
A1B2C3D40123456789ABC003 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A3B2C3D40123456789ABC002 /* NetworkExtension.framework */; };
A1B2C3D40123456789ABC004 /* StikDebugTunnel.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A3B2C3D40123456789ABC001 /* StikDebugTunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -26,6 +30,13 @@
remoteGlobalIDString = DC6F1D362D94EADD0071B2B6;
remoteInfo = StikJIT;
};
A2B2C3D40123456789ABC001 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DC6F1D2F2D94EADD0071B2B6 /* Project object */;
proxyType = 1;
remoteGlobalIDString = A5B2C3D40123456789ABC001;
remoteInfo = StikDebugTunnel;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@ -35,6 +46,7 @@
dstPath = "";
dstSubfolderSpec = 13;
files = (
A1B2C3D40123456789ABC004 /* StikDebugTunnel.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
@ -47,6 +59,10 @@
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; };
A3B2C3D40123456789ABC001 /* StikDebugTunnel.appex */ = {isa = PBXFileReference; explicitFileType = wrapper.app-extension; includeInIndex = 0; path = StikDebugTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; };
A3B2C3D40123456789ABC002 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
A3B2C3D40123456789ABC003 /* StikDebugTunnelConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StikJIT/Tunnel/StikDebugTunnelConfiguration.swift; sourceTree = "<group>"; };
A3B2C3D40123456789ABC004 /* IPv4PacketRewriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StikJIT/Tunnel/IPv4PacketRewriter.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@ -78,6 +94,11 @@
path = StikJITUITests;
sourceTree = "<group>";
};
A4B2C3D40123456789ABC001 /* StikDebugTunnel */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = StikDebugTunnel;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@ -101,6 +122,14 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
A6B2C3D40123456789ABC002 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A1B2C3D40123456789ABC003 /* NetworkExtension.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -111,6 +140,9 @@
isa = PBXGroup;
children = (
DC6F1D392D94EADD0071B2B6 /* StikJIT */,
A4B2C3D40123456789ABC001 /* StikDebugTunnel */,
A3B2C3D40123456789ABC003 /* StikDebugTunnelConfiguration.swift */,
A3B2C3D40123456789ABC004 /* IPv4PacketRewriter.swift */,
DC6F1D4B2D94EADF0071B2B6 /* StikJITTests */,
DC6F1D552D94EADF0071B2B6 /* StikJITUITests */,
DC6F1D752D94EB620071B2B6 /* Frameworks */,
@ -124,6 +156,7 @@
DC6F1D372D94EADD0071B2B6 /* StikDebug.app */,
DC6F1D482D94EADF0071B2B6 /* StikDebugTests.xctest */,
DC6F1D522D94EADF0071B2B6 /* StikDebugUITests.xctest */,
A3B2C3D40123456789ABC001 /* StikDebugTunnel.appex */,
);
name = Products;
sourceTree = "<group>";
@ -133,6 +166,7 @@
children = (
DC139F6D2DE97EA400F63846 /* WidgetKit.framework */,
DC139F6F2DE97EA400F63846 /* SwiftUI.framework */,
A3B2C3D40123456789ABC002 /* NetworkExtension.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -152,6 +186,7 @@
buildRules = (
);
dependencies = (
A7B2C3D40123456789ABC001 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
DC6F1D392D94EADD0071B2B6 /* StikJIT */,
@ -211,6 +246,26 @@
productReference = DC6F1D522D94EADF0071B2B6 /* StikDebugUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
A5B2C3D40123456789ABC001 /* StikDebugTunnel */ = {
isa = PBXNativeTarget;
buildConfigurationList = A9B2C3D40123456789ABC001 /* Build configuration list for PBXNativeTarget "StikDebugTunnel" */;
buildPhases = (
A6B2C3D40123456789ABC001 /* Sources */,
A6B2C3D40123456789ABC002 /* Frameworks */,
A6B2C3D40123456789ABC003 /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
A4B2C3D40123456789ABC001 /* StikDebugTunnel */,
);
name = StikDebugTunnel;
productName = StikDebugTunnel;
productReference = A3B2C3D40123456789ABC001 /* StikDebugTunnel.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@ -233,6 +288,9 @@
CreatedOnToolsVersion = 16.2;
TestTargetID = DC6F1D362D94EADD0071B2B6;
};
A5B2C3D40123456789ABC001 = {
CreatedOnToolsVersion = 16.2;
};
};
};
buildConfigurationList = DC6F1D322D94EADD0071B2B6 /* Build configuration list for PBXProject "StikDebug" */;
@ -257,6 +315,7 @@
DC6F1D362D94EADD0071B2B6 /* StikDebug */,
DC6F1D472D94EADF0071B2B6 /* StikDebugTests */,
DC6F1D512D94EADF0071B2B6 /* StikDebugUITests */,
A5B2C3D40123456789ABC001 /* StikDebugTunnel */,
);
};
/* End PBXProject section */
@ -283,6 +342,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
A6B2C3D40123456789ABC003 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -307,6 +373,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
A6B2C3D40123456789ABC001 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A1B2C3D40123456789ABC001 /* StikDebugTunnelConfiguration.swift in StikDebugTunnel Sources */,
A1B2C3D40123456789ABC002 /* IPv4PacketRewriter.swift in StikDebugTunnel Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@ -320,9 +395,58 @@
target = DC6F1D362D94EADD0071B2B6 /* StikDebug */;
targetProxy = DC6F1D532D94EADF0071B2B6 /* PBXContainerItemProxy */;
};
A7B2C3D40123456789ABC001 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = A5B2C3D40123456789ABC001 /* StikDebugTunnel */;
targetProxy = A2B2C3D40123456789ABC001 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
A8B2C3D40123456789ABC001 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_ENTITLEMENTS = StikDebugTunnel/StikDebugTunnel.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = StikDebugTunnel/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
PRODUCT_BUNDLE_IDENTIFIER = com.stik.stikdebug.tunnel;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
A8B2C3D40123456789ABC002 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_ENTITLEMENTS = StikDebugTunnel/StikDebugTunnel.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = StikDebugTunnel/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
PRODUCT_BUNDLE_IDENTIFIER = com.stik.stikdebug.tunnel;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
DC6F1D5A2D94EADF0071B2B6 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -707,6 +831,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
A9B2C3D40123456789ABC001 /* Build configuration list for PBXNativeTarget "StikDebugTunnel" */ = {
isa = XCConfigurationList;
buildConfigurations = (
A8B2C3D40123456789ABC001 /* Debug */,
A8B2C3D40123456789ABC002 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */

View 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>

View file

@ -0,0 +1,97 @@
//
// PacketTunnelProvider.swift
// StikDebugTunnel
//
import NetworkExtension
final class PacketTunnelProvider: NEPacketTunnelProvider {
private var interfaceIP = StikDebugTunnelConfiguration.default.interfaceIP
private var peerIP = StikDebugTunnelConfiguration.default.peerIP
private var packetLoopActive = false
override func startTunnel(
options: [String: NSObject]?,
completionHandler: @escaping (Error?) -> Void
) {
let providerConfiguration =
(protocolConfiguration as? NETunnelProviderProtocol)?.providerConfiguration
if let value = options?[StikDebugTunnelConfiguration.interfaceIPKey] as? String
?? providerConfiguration?[StikDebugTunnelConfiguration.interfaceIPKey] as? String {
interfaceIP = value
}
if let value = options?[StikDebugTunnelConfiguration.peerIPKey] as? String
?? providerConfiguration?[StikDebugTunnelConfiguration.peerIPKey] as? String {
peerIP = value
}
let ipv4Settings = NEIPv4Settings(
addresses: [interfaceIP],
subnetMasks: ["255.255.255.0"]
)
ipv4Settings.includedRoutes = [
NEIPv4Route(
destinationAddress: peerIP,
subnetMask: "255.255.255.255"
)
]
ipv4Settings.excludedRoutes = [.default()]
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: peerIP)
settings.ipv4Settings = ipv4Settings
setTunnelNetworkSettings(settings) { [weak self] error in
guard let self else {
completionHandler(error)
return
}
if let error {
completionHandler(error)
return
}
packetLoopActive = true
readPackets()
completionHandler(nil)
}
}
override func stopTunnel(
with reason: NEProviderStopReason,
completionHandler: @escaping () -> Void
) {
packetLoopActive = false
completionHandler()
}
private func readPackets() {
guard packetLoopActive else { return }
packetFlow.readPackets { [weak self] packets, protocols in
guard let self, self.packetLoopActive else { return }
var rewrittenPackets = packets
for index in rewrittenPackets.indices {
guard protocols[index].int32Value == AF_INET else { continue }
rewrittenPackets[index].withUnsafeMutableBytes { rawBuffer in
guard let baseAddress = rawBuffer.baseAddress else { return }
var packet = Array(
UnsafeRawBufferPointer(
start: baseAddress,
count: rawBuffer.count
)
)
IPv4PacketRewriter.swapEndpoints(in: &packet)
packet.withUnsafeBytes { rewrittenBuffer in
rawBuffer.copyBytes(from: rewrittenBuffer)
}
}
}
self.packetFlow.writePackets(rewrittenPackets, withProtocols: protocols)
self.readPackets()
}
}
}

View 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.stikdebug</string>
</array>
</dict>
</plist>