Simplified process startup on macOS and Linux

This commit is contained in:
Conrad Kramer 2024-01-21 16:18:13 -08:00
parent 9e03c9680c
commit b462f3fb6c
40 changed files with 1343 additions and 1157 deletions

View file

@ -39,10 +39,10 @@ extension Tunnel {
var isOn: Binding<Bool> {
Binding {
switch self.status {
case .unknown, .disabled, .disconnecting, .disconnected, .invalid, .permissionRequired, .configurationReadWriteFailed:
return false
case .connecting, .reasserting, .connected:
return true
true
default:
false
}
} set: { newValue in
switch (self.status, newValue) {

View file

@ -1,11 +1,13 @@
import Combine
import BurrowShared
import NetworkExtension
import SwiftUI
@Observable class Tunnel {
@Observable
class Tunnel {
private(set) var status: Status = .unknown
private var error: NEVPNError?
private let logger = Logger.logger(for: Tunnel.self)
private let bundleIdentifier: String
private let configure: (NETunnelProviderManager, NETunnelProviderProtocol) -> Void
private var tasks: [Task<Void, Error>] = []
@ -49,33 +51,34 @@ import SwiftUI
self.bundleIdentifier = bundleIdentifier
self.configure = configure
listenForUpdates()
Task { await update() }
}
private func listenForUpdates() {
let center = NotificationCenter.default
let statusTask = Task {
for try await _ in center.notifications(named: .NEVPNStatusDidChange).map({ _ in () }) {
status = currentStatus
}
}
let configurationTask = Task {
let configurationChanged = Task {
for try await _ in center.notifications(named: .NEVPNConfigurationChange).map({ _ in () }) {
await update()
}
}
tasks = [statusTask, configurationTask]
let statusChanged = Task {
for try await _ in center.notifications(named: .NEVPNStatusDidChange).map({ _ in () }) {
await MainActor.run {
status = currentStatus
}
}
}
tasks = [configurationChanged, statusChanged]
Task { await update() }
}
private func update() async {
do {
let updated = try await NETunnelProviderManager.managers
await MainActor.run { managers = updated }
await MainActor.run {
managers = updated
}
} catch let vpnError as NEVPNError {
error = vpnError
} catch {
print(error)
logger.error("Failed to update VPN configurations: \(error)")
}
}
@ -117,9 +120,7 @@ import SwiftUI
}
deinit {
for task in tasks {
task.cancel()
}
tasks.forEach { $0.cancel() }
}
}
@ -127,19 +128,19 @@ extension NEVPNConnection {
var tunnelStatus: Tunnel.Status {
switch status {
case .connected:
return .connected(connectedDate!)
.connected(connectedDate!)
case .connecting:
return .connecting
.connecting
case .disconnecting:
return .disconnecting
.disconnecting
case .disconnected:
return .disconnected
.disconnected
case .reasserting:
return .reasserting
.reasserting
case .invalid:
return .invalid
.invalid
@unknown default:
return .unknown
.unknown
}
}
}

View file

@ -8,14 +8,20 @@
/* Begin PBXBuildFile section */
0B28F1562ABF463A000D44B0 /* DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B28F1552ABF463A000D44B0 /* DataTypes.swift */; };
0B46E8E02AC918CA00BA2A3C /* BurrowIpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B46E8DF2AC918CA00BA2A3C /* BurrowIpc.swift */; };
0B46E8E02AC918CA00BA2A3C /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B46E8DF2AC918CA00BA2A3C /* Client.swift */; };
43AA26D82A10004900F14CE6 /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuView.swift */; };
D00117312B2FFFC900D87C25 /* NWConnection+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */; };
D00117332B3001A400D87C25 /* NewlineProtocolFramer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */; };
D001173B2B30341C00D87C25 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D001173A2B30341C00D87C25 /* Logging.swift */; };
D00117442B30372900D87C25 /* libBurrowShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D00117382B30341C00D87C25 /* libBurrowShared.a */; };
D00117452B30372C00D87C25 /* libBurrowShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D00117382B30341C00D87C25 /* libBurrowShared.a */; };
D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00AA8962A4669BC005C8102 /* AppDelegate.swift */; };
D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */; };
D020F65D29E4A697002790F6 /* BurrowNetworkExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */; };
D05B9F7829E39EEC008CB1F9 /* TunnelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7729E39EEC008CB1F9 /* TunnelView.swift */; };
D05B9F7A29E39EED008CB1F9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D05B9F7929E39EED008CB1F9 /* Assets.xcassets */; };
D08252762B5C9FC4005DA378 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08252752B5C9FC4005DA378 /* Constants.swift */; };
D0BCC5FD2A086D4700AD070D /* NetworkExtension+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BCC5FC2A086D4700AD070D /* NetworkExtension+Async.swift */; };
D0BCC5FF2A086E1C00AD070D /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BCC5FE2A086E1C00AD070D /* Status.swift */; };
D0BCC6082A0981FE00AD070D /* Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B98FC629FDC5B5004E7149 /* Tunnel.swift */; };
@ -24,6 +30,20 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
D00117462B30373100D87C25 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */;
proxyType = 1;
remoteGlobalIDString = D00117372B30341C00D87C25;
remoteInfo = Shared;
};
D00117482B30373500D87C25 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */;
proxyType = 1;
remoteGlobalIDString = D00117372B30341C00D87C25;
remoteInfo = Shared;
};
D020F65B29E4A697002790F6 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */;
@ -49,8 +69,14 @@
/* Begin PBXFileReference section */
0B28F1552ABF463A000D44B0 /* DataTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTypes.swift; sourceTree = "<group>"; };
0B46E8DF2AC918CA00BA2A3C /* BurrowIpc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowIpc.swift; sourceTree = "<group>"; };
0B46E8DF2AC918CA00BA2A3C /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = "<group>"; };
43AA26D72A10004900F14CE6 /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = "<group>"; };
D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NWConnection+Async.swift"; sourceTree = "<group>"; };
D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewlineProtocolFramer.swift; sourceTree = "<group>"; };
D00117382B30341C00D87C25 /* libBurrowShared.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBurrowShared.a; sourceTree = BUILT_PRODUCTS_DIR; };
D001173A2B30341C00D87C25 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; };
D00117412B30347800D87C25 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
D00117422B30348D00D87C25 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = "<group>"; };
D00AA8962A4669BC005C8102 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
D020F63D29E4A1FF002790F6 /* Identity.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Identity.xcconfig; sourceTree = "<group>"; };
D020F64029E4A1FF002790F6 /* Compiler.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Compiler.xcconfig; sourceTree = "<group>"; };
@ -70,6 +96,8 @@
D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowApp.swift; sourceTree = "<group>"; };
D05B9F7729E39EEC008CB1F9 /* TunnelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelView.swift; sourceTree = "<group>"; };
D05B9F7929E39EED008CB1F9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
D08252742B5C9DEB005DA378 /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = "<group>"; };
D08252752B5C9FC4005DA378 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
D0B98FBF29FD8072004E7149 /* build-rust.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "build-rust.sh"; sourceTree = "<group>"; };
D0B98FC629FDC5B5004E7149 /* Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tunnel.swift; sourceTree = "<group>"; };
D0B98FD829FDDB6F004E7149 /* libburrow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = libburrow.h; sourceTree = "<group>"; };
@ -80,10 +108,18 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
D00117352B30341C00D87C25 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
D020F65029E4A697002790F6 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D00117442B30372900D87C25 /* libBurrowShared.a in Frameworks */,
D0BCC6092A09A03E00AD070D /* libburrow.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -92,6 +128,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D00117452B30372C00D87C25 /* libBurrowShared.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -106,6 +143,33 @@
path = Menu;
sourceTree = "<group>";
};
D00117392B30341C00D87C25 /* Shared */ = {
isa = PBXGroup;
children = (
D001173A2B30341C00D87C25 /* Logging.swift */,
D08252752B5C9FC4005DA378 /* Constants.swift */,
D00117422B30348D00D87C25 /* Shared.xcconfig */,
D001173F2B30347800D87C25 /* Constants */,
);
path = Shared;
sourceTree = "<group>";
};
D001173F2B30347800D87C25 /* Constants */ = {
isa = PBXGroup;
children = (
D08252742B5C9DEB005DA378 /* Constants.h */,
D00117412B30347800D87C25 /* module.modulemap */,
);
path = Constants;
sourceTree = "<group>";
};
D00117432B30372900D87C25 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
D020F63C29E4A1FF002790F6 /* Configuration */ = {
isa = PBXGroup;
children = (
@ -122,12 +186,14 @@
isa = PBXGroup;
children = (
D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */,
0B46E8DF2AC918CA00BA2A3C /* Client.swift */,
0B28F1552ABF463A000D44B0 /* DataTypes.swift */,
D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */,
D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */,
D020F65929E4A697002790F6 /* Info.plist */,
D020F66729E4A95D002790F6 /* NetworkExtension-iOS.entitlements */,
D020F66629E4A95D002790F6 /* NetworkExtension-macOS.entitlements */,
D020F66229E4A6E5002790F6 /* NetworkExtension.xcconfig */,
0B28F1552ABF463A000D44B0 /* DataTypes.swift */,
0B46E8DF2AC918CA00BA2A3C /* BurrowIpc.swift */,
D0B98FD729FDDB57004E7149 /* libburrow */,
);
path = NetworkExtension;
@ -138,8 +204,10 @@
children = (
D05B9F7429E39EEC008CB1F9 /* App */,
D020F65629E4A697002790F6 /* NetworkExtension */,
D00117392B30341C00D87C25 /* Shared */,
D020F63C29E4A1FF002790F6 /* Configuration */,
D05B9F7329E39EEC008CB1F9 /* Products */,
D00117432B30372900D87C25 /* Frameworks */,
);
sourceTree = "<group>";
};
@ -148,6 +216,7 @@
children = (
D05B9F7229E39EEC008CB1F9 /* Burrow.app */,
D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */,
D00117382B30341C00D87C25 /* libBurrowShared.a */,
);
name = Products;
sourceTree = "<group>";
@ -184,6 +253,23 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
D00117372B30341C00D87C25 /* Shared */ = {
isa = PBXNativeTarget;
buildConfigurationList = D001173C2B30341C00D87C25 /* Build configuration list for PBXNativeTarget "Shared" */;
buildPhases = (
D00117342B30341C00D87C25 /* Sources */,
D00117352B30341C00D87C25 /* Frameworks */,
);
buildRules = (
);
dependencies = (
D082527D2B5DEB80005DA378 /* PBXTargetDependency */,
);
name = Shared;
productName = Shared;
productReference = D00117382B30341C00D87C25 /* libBurrowShared.a */;
productType = "com.apple.product-type.library.static";
};
D020F65229E4A697002790F6 /* NetworkExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = D020F65E29E4A697002790F6 /* Build configuration list for PBXNativeTarget "NetworkExtension" */;
@ -196,7 +282,8 @@
buildRules = (
);
dependencies = (
D08252712B5C3E2E005DA378 /* PBXTargetDependency */,
D08252792B5DEB78005DA378 /* PBXTargetDependency */,
D00117492B30373500D87C25 /* PBXTargetDependency */,
);
name = NetworkExtension;
productName = BurrowNetworkExtension;
@ -215,7 +302,8 @@
buildRules = (
);
dependencies = (
D08252732B5C3E33005DA378 /* PBXTargetDependency */,
D082527B2B5DEB7D005DA378 /* PBXTargetDependency */,
D00117472B30373100D87C25 /* PBXTargetDependency */,
D020F65C29E4A697002790F6 /* PBXTargetDependency */,
);
name = App;
@ -230,9 +318,12 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1430;
LastUpgradeCheck = 1510;
LastSwiftUpdateCheck = 1510;
LastUpgradeCheck = 1430;
TargetAttributes = {
D00117372B30341C00D87C25 = {
CreatedOnToolsVersion = 15.1;
};
D020F65229E4A697002790F6 = {
CreatedOnToolsVersion = 14.3;
};
@ -251,7 +342,7 @@
);
mainGroup = D05B9F6929E39EEC008CB1F9;
packageReferences = (
D082526F2B5C3E23005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */,
D08252772B5DEB6E005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */,
);
productRefGroup = D05B9F7329E39EEC008CB1F9 /* Products */;
projectDirPath = "";
@ -259,6 +350,7 @@
targets = (
D05B9F7129E39EEC008CB1F9 /* App */,
D020F65229E4A697002790F6 /* NetworkExtension */,
D00117372B30341C00D87C25 /* Shared */,
);
};
/* End PBXProject section */
@ -306,12 +398,23 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
D00117342B30341C00D87C25 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D001173B2B30341C00D87C25 /* Logging.swift in Sources */,
D08252762B5C9FC4005DA378 /* Constants.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D020F64F29E4A697002790F6 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D00117332B3001A400D87C25 /* NewlineProtocolFramer.swift in Sources */,
0B28F1562ABF463A000D44B0 /* DataTypes.swift in Sources */,
0B46E8E02AC918CA00BA2A3C /* BurrowIpc.swift in Sources */,
D00117312B2FFFC900D87C25 /* NWConnection+Async.swift in Sources */,
0B46E8E02AC918CA00BA2A3C /* Client.swift in Sources */,
D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -333,22 +436,50 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
D00117472B30373100D87C25 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D00117372B30341C00D87C25 /* Shared */;
targetProxy = D00117462B30373100D87C25 /* PBXContainerItemProxy */;
};
D00117492B30373500D87C25 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D00117372B30341C00D87C25 /* Shared */;
targetProxy = D00117482B30373500D87C25 /* PBXContainerItemProxy */;
};
D020F65C29E4A697002790F6 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D020F65229E4A697002790F6 /* NetworkExtension */;
targetProxy = D020F65B29E4A697002790F6 /* PBXContainerItemProxy */;
};
D08252712B5C3E2E005DA378 /* PBXTargetDependency */ = {
D08252792B5DEB78005DA378 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = D08252702B5C3E2E005DA378 /* SwiftLintPlugin */;
productRef = D08252782B5DEB78005DA378 /* SwiftLintPlugin */;
};
D08252732B5C3E33005DA378 /* PBXTargetDependency */ = {
D082527B2B5DEB7D005DA378 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = D08252722B5C3E33005DA378 /* SwiftLintPlugin */;
productRef = D082527A2B5DEB7D005DA378 /* SwiftLintPlugin */;
};
D082527D2B5DEB80005DA378 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = D082527C2B5DEB80005DA378 /* SwiftLintPlugin */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
D001173D2B30341C00D87C25 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D00117422B30348D00D87C25 /* Shared.xcconfig */;
buildSettings = {
};
name = Debug;
};
D001173E2B30341C00D87C25 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D00117422B30348D00D87C25 /* Shared.xcconfig */;
buildSettings = {
};
name = Release;
};
D020F65F29E4A697002790F6 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D020F66229E4A6E5002790F6 /* NetworkExtension.xcconfig */;
@ -394,6 +525,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
D001173C2B30341C00D87C25 /* Build configuration list for PBXNativeTarget "Shared" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D001173D2B30341C00D87C25 /* Debug */,
D001173E2B30341C00D87C25 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D020F65E29E4A697002790F6 /* Build configuration list for PBXNativeTarget "NetworkExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@ -424,7 +564,7 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
D082526F2B5C3E23005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */ = {
D08252772B5DEB6E005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/realm/SwiftLint.git";
requirement = {
@ -435,14 +575,19 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
D08252702B5C3E2E005DA378 /* SwiftLintPlugin */ = {
D08252782B5DEB78005DA378 /* SwiftLintPlugin */ = {
isa = XCSwiftPackageProductDependency;
package = D082526F2B5C3E23005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */;
package = D08252772B5DEB6E005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */;
productName = "plugin:SwiftLintPlugin";
};
D08252722B5C3E33005DA378 /* SwiftLintPlugin */ = {
D082527A2B5DEB7D005DA378 /* SwiftLintPlugin */ = {
isa = XCSwiftPackageProductDependency;
package = D082526F2B5C3E23005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */;
package = D08252772B5DEB6E005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */;
productName = "plugin:SwiftLintPlugin";
};
D082527C2B5DEB80005DA378 /* SwiftLintPlugin */ = {
isa = XCSwiftPackageProductDependency;
package = D08252772B5DEB6E005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */;
productName = "plugin:SwiftLintPlugin";
};
/* End XCSwiftPackageProductDependency section */

View file

@ -1,5 +1,5 @@
SKIP_INSTALL = NO
MERGED_BINARY_TYPE = manual
LD_RUNPATH_SEARCH_PATHS[sdk=iphone*] = $(inherited) @executable_path/Frameworks
LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks

View file

@ -21,7 +21,7 @@ CODE_SIGN_IDENTITY = Apple Development
INFOPLIST_FILE = Configuration/Info.plist
GENERATE_INFOPLIST_FILE = YES
INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2023 Hack Club
INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2023-2024 Hack Club
INFOPLIST_KEY_CFBundleDisplayName = Burrow
ENABLE_BITCODE = NO

View file

@ -1,2 +1,4 @@
MERGED_BINARY_TYPE = manual
LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @executable_path/../../Frameworks
LD_RUNPATH_SEARCH_PATHS[sdk=macos*] = $(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks

View file

@ -1,133 +0,0 @@
import Foundation
import Network
import os
final class LineProtocol: NWProtocolFramerImplementation {
static let definition = NWProtocolFramer.Definition(implementation: LineProtocol.self)
static let label = "Lines"
init(framer: NWProtocolFramer.Instance) { }
func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult { .ready }
func stop(framer: NWProtocolFramer.Instance) -> Bool { true }
func wakeup(framer: NWProtocolFramer.Instance) { }
func cleanup(framer: NWProtocolFramer.Instance) { }
func lines(from buffer: UnsafeMutableRawBufferPointer?) -> (lines: [Data], size: Int)? {
guard let buffer = buffer else { return nil }
let lines = buffer
.split(separator: 10)
guard !lines.isEmpty else { return nil }
let size = lines
.lazy
.map(\.count)
.reduce(0, +) + lines.count
let strings = lines
.lazy
.map { Data($0) }
return (lines: Array(strings), size: size)
}
func handleInput(framer: NWProtocolFramer.Instance) -> Int {
var result: [Data] = []
_ = framer.parseInput(minimumIncompleteLength: 1, maximumLength: 16_000) { buffer, _ in
guard let (lines, size) = lines(from: buffer) else {
return 0
}
result = lines
return size
}
for line in result {
framer.deliverInput(data: line, message: .init(instance: framer), isComplete: true)
}
return 0
}
func handleOutput(
framer: NWProtocolFramer.Instance,
message: NWProtocolFramer.Message,
messageLength: Int,
isComplete: Bool
) {
do {
try framer.writeOutputNoCopy(length: messageLength)
} catch {
}
}
}
extension NWConnection {
func receiveMessage() async throws -> (Data?, NWConnection.ContentContext?, Bool) {
try await withUnsafeThrowingContinuation { continuation in
receiveMessage { completeContent, contentContext, isComplete, error in
if let error = error {
continuation.resume(throwing: error)
}
continuation.resume(returning: (completeContent, contentContext, isComplete))
}
}
}
func send_raw(_ request: Data) async throws -> Data {
try await withCheckedThrowingContinuation { continuation in
let comp: NWConnection.SendCompletion = .contentProcessed {error in
if let error = error {
continuation.resume(with: .failure(error))
} else {
continuation.resume(with: .success(request))
}
}
self.send(content: request, completion: comp)
}
}
}
final class BurrowIpc {
let connection: NWConnection
private var generator = SystemRandomNumberGenerator()
private var logger: Logger
init(logger: Logger) {
let params = NWParameters.tcp
params.defaultProtocolStack
.applicationProtocols
.insert(NWProtocolFramer.Options(definition: LineProtocol.definition), at: 0)
let connection = NWConnection(to: .unix(path: "burrow.sock"), using: params)
connection.start(queue: .global())
self.connection = connection
self.logger = logger
}
func send<T: Request, U: Decodable>(_ request: T) async throws -> U {
do {
let id: UInt = generator.next(upperBound: UInt.max)
var copy = request
copy.id = id
var data = try JSONEncoder().encode(request)
data.append(contentsOf: [10])
_ = try await self.connection.send_raw(data)
return try JSONDecoder().decode(Response<U>.self, from: data).result
} catch {
throw error
}
}
func receive_raw() async throws -> Data {
let (completeContent, _, _) = try await connection.receiveMessage()
self.logger.info("Received raw message response")
guard let data = completeContent else {
throw BurrowError.resultIsNone
}
return data
}
func request<U: Decodable>(_ request: any Request, type: U.Type) async throws -> U {
do {
var data: Data = try JSONEncoder().encode(request)
data.append(contentsOf: [10])
_ = try await self.connection.send_raw(data)
self.logger.debug("message sent")
let receivedData = try await receive_raw()
self.logger.info("Received result: \(String(decoding: receivedData, as: UTF8.self))")
return try self.parse_response(receivedData)
} catch {
throw error
}
}
func parse_response<U: Decodable>(_ response: Data) throws -> U {
try JSONDecoder().decode(U.self, from: response)
}
}

View file

@ -0,0 +1,60 @@
import BurrowShared
import Foundation
import Network
final class Client {
let connection: NWConnection
private let logger: Logger = Logger.logger(for: Client.self)
private var generator = SystemRandomNumberGenerator()
convenience init() throws {
self.init(url: try Constants.socketURL)
}
init(url: URL) {
let endpoint: NWEndpoint
if url.isFileURL {
endpoint = .unix(path: url.path(percentEncoded: false))
} else {
endpoint = .url(url)
}
let parameters = NWParameters.tcp
parameters.defaultProtocolStack
.applicationProtocols
.insert(NWProtocolFramer.Options(definition: NewlineProtocolFramer.definition), at: 0)
connection = NWConnection(to: endpoint, using: parameters)
connection.start(queue: .global())
}
func request<U: Decodable>(_ request: any Request, type: U.Type = U.self) async throws -> U {
do {
var copy = request
copy.id = generator.next(upperBound: UInt.max)
let content = try JSONEncoder().encode(copy)
logger.debug("> \(String(decoding: content, as: UTF8.self))")
try await self.connection.send(content: content)
let (response, _, _) = try await connection.receiveMessage()
logger.debug("< \(String(decoding: response, as: UTF8.self))")
return try JSONDecoder().decode(U.self, from: response)
} catch {
logger.error("\(error, privacy: .public)")
throw error
}
}
deinit {
connection.cancel()
}
}
extension Constants {
static var socketURL: URL {
get throws {
try groupContainerURL.appending(component: "burrow.sock", directoryHint: .notDirectory)
}
}
}

View file

@ -8,10 +8,11 @@ enum BurrowError: Error {
case resultIsNone
}
protocol Request: Codable where CommandT: Codable {
associatedtype CommandT
protocol Request: Codable where Command: Codable {
associatedtype Command
var id: UInt { get set }
var command: CommandT { get set }
var command: Command { get set }
}
struct BurrowSingleCommand: Request {
@ -38,13 +39,6 @@ struct BurrowStartRequest: Codable {
let Start: StartOptions
}
func start_req_fd(id: UInt) -> BurrowRequest<BurrowStartRequest> {
let command = BurrowStartRequest(Start: BurrowStartRequest.StartOptions(
tun: BurrowStartRequest.TunOptions(name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: nil)
))
return BurrowRequest(id: id, command: command)
}
struct Response<T>: Decodable where T: Decodable {
var id: UInt
var result: T

View file

@ -0,0 +1,32 @@
import Foundation
import Network
extension NWConnection {
// swiftlint:disable:next large_tuple
func receiveMessage() async throws -> (Data, NWConnection.ContentContext?, Bool) {
try await withUnsafeThrowingContinuation { continuation in
receiveMessage { completeContent, contentContext, isComplete, error in
if let error {
continuation.resume(throwing: error)
} else {
guard let completeContent = completeContent else {
fatalError("Both error and completeContent were nil")
}
continuation.resume(returning: (completeContent, contentContext, isComplete))
}
}
}
}
func send(content: Data) async throws {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
send(content: content, completion: .contentProcessed { error in
if let error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: ())
}
})
}
}
}

View file

@ -2,17 +2,19 @@
<!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.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(APP_GROUP_IDENTIFIER)</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View file

@ -6,6 +6,6 @@ PRODUCT_BUNDLE_IDENTIFIER = $(NETWORK_EXTENSION_BUNDLE_IDENTIFIER)
INFOPLIST_FILE = NetworkExtension/Info.plist
CODE_SIGN_ENTITLEMENTS = NetworkExtension/NetworkExtension-iOS.entitlements
CODE_SIGN_ENTITLEMENTS[sdk=macos*] = NetworkExtension/NetworkExtension-macOS.entitlements
CODE_SIGN_ENTITLEMENTS[sdk=macosx*] = NetworkExtension/NetworkExtension-macOS.entitlements
SWIFT_INCLUDE_PATHS = $(inherited) $(PROJECT_DIR)/NetworkExtension

View file

@ -0,0 +1,54 @@
import Foundation
import Network
final class NewlineProtocolFramer: NWProtocolFramerImplementation {
private static let delimeter: UInt8 = 10 // `\n`
static let definition = NWProtocolFramer.Definition(implementation: NewlineProtocolFramer.self)
static let label = "Lines"
init(framer: NWProtocolFramer.Instance) { }
func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult { .ready }
func stop(framer: NWProtocolFramer.Instance) -> Bool { true }
func wakeup(framer: NWProtocolFramer.Instance) { }
func cleanup(framer: NWProtocolFramer.Instance) { }
func handleInput(framer: NWProtocolFramer.Instance) -> Int {
while true {
var result: [Data] = []
let parsed = framer.parseInput(minimumIncompleteLength: 1, maximumLength: 16_000) { buffer, _ in
guard let buffer else { return 0 }
var lines = buffer
.split(separator: Self.delimeter, omittingEmptySubsequences: false)
.map { Data($0) }
guard lines.count > 1 else { return 0 }
_ = lines.popLast()
result = lines
return lines.reduce(lines.count) { $0 + $1.count }
}
guard parsed && !result.isEmpty else { break }
for line in result {
framer.deliverInput(data: line, message: .init(instance: framer), isComplete: true)
}
}
return 0
}
func handleOutput(
framer: NWProtocolFramer.Instance,
message: NWProtocolFramer.Message,
messageLength: Int,
isComplete: Bool
) {
do {
try framer.writeOutputNoCopy(length: messageLength)
framer.writeOutput(data: [Self.delimeter])
} catch {
}
}
}

View file

@ -1,51 +1,49 @@
import BurrowShared
import libburrow
import NetworkExtension
import os
class PacketTunnelProvider: NEPacketTunnelProvider {
let logger = Logger(subsystem: "com.hackclub.burrow", category: "frontend")
var client: BurrowIpc?
var osInitialized = false
private let logger = Logger.logger(for: PacketTunnelProvider.self)
override func startTunnel(options: [String: NSObject]? = nil) async throws {
logger.log("Starting tunnel")
if !osInitialized {
libburrow.initialize_oslog()
osInitialized = true
}
libburrow.start_srv()
client = BurrowIpc(logger: logger)
logger.info("Started server")
do {
let command = BurrowSingleCommand(id: 0, command: "ServerConfig")
guard let data = try await client?.request(command, type: Response<BurrowResult<ServerConfigData>>.self)
else {
throw BurrowError.cantParseResult
}
libburrow.spawnInProcess(socketPath: try Constants.socketURL.path)
let client = try Client()
let command = BurrowRequest(id: 0, command: "ServerConfig")
let data = try await client.request(command, type: Response<BurrowResult<ServerConfigData>>.self)
let encoded = try JSONEncoder().encode(data.result)
self.logger.log("Received final data: \(String(decoding: encoded, as: UTF8.self))")
guard let serverconfig = data.result.Ok else {
throw BurrowError.resultIsError
}
guard let tunNs = self.generateTunSettings(from: serverconfig) else {
guard let tunNs = generateTunSettings(from: serverconfig) else {
throw BurrowError.addrDoesntExist
}
try await self.setTunnelNetworkSettings(tunNs)
self.logger.info("Set remote tunnel address to \(tunNs.tunnelRemoteAddress)")
// let tunFd = self.packetFlow.value(forKeyPath: "socket.fileDescriptor") as! Int;
// self.logger.info("Found File Descriptor: \(tunFd)")
let startCommand = start_req_fd(id: 1)
guard let data = try await client?.request(startCommand, type: Response<BurrowResult<String>>.self)
else {
throw BurrowError.cantParseResult
}
let encodedStartRes = try JSONEncoder().encode(data.result)
self.logger.log("Received start server response: \(String(decoding: encodedStartRes, as: UTF8.self))")
let startRequest = BurrowRequest(
id: .random(in: (.min)..<(.max)),
command: BurrowStartRequest(
Start: BurrowStartRequest.StartOptions(
tun: BurrowStartRequest.TunOptions(
name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: nil
)
)
)
)
let response = try await client.request(startRequest, type: Response<BurrowResult<String>>.self)
self.logger.log("Received start server response: \(String(describing: response.result))")
} catch {
self.logger.error("An error occurred: \(error)")
self.logger.error("Failed to start tunnel: \(error)")
throw error
}
}
private func generateTunSettings(from: ServerConfigData) -> NETunnelNetworkSettings? {
let cfig = from.ServerConfig
guard let addr = cfig.address else {
@ -57,13 +55,4 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
logger.log("Initialized ipv4 settings: \(nst.ipv4Settings)")
return nst
}
override func stopTunnel(with reason: NEProviderStopReason) async {
}
override func handleAppMessage(_ messageData: Data) async -> Data? {
messageData
}
override func sleep() async {
}
override func wake() {
}
}

View file

@ -1,2 +1,2 @@
void start_srv();
void initialize_oslog();
__attribute__((__swift_name__("spawnInProcess(socketPath:)")))
extern void spawn_in_process(const char * __nullable path);

View file

@ -0,0 +1,22 @@
@_implementationOnly import Constants
public enum Constants {
enum Error: Swift.Error {
case invalidAppGroupIdentifier
}
public static let bundleIdentifier = AppBundleIdentifier
public static let appGroupIdentifier = AppGroupIdentifier
public static var groupContainerURL: URL {
get throws { try _groupContainerURL.get() }
}
private static let _groupContainerURL: Result<URL, Error> = {
guard let groupContainerURL = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else {
return .failure(.invalidAppGroupIdentifier)
}
return .success(groupContainerURL)
}()
}

View file

@ -0,0 +1,11 @@
#import <Foundation/Foundation.h>
#define MACRO_STRING_(m) #m
#define MACRO_STRING(m) @MACRO_STRING_(m)
NS_ASSUME_NONNULL_BEGIN
static NSString * const AppBundleIdentifier = MACRO_STRING(APP_BUNDLE_IDENTIFIER);
static NSString * const AppGroupIdentifier = MACRO_STRING(APP_GROUP_IDENTIFIER);
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,4 @@
module Constants {
header "Constants.h"
export *
}

View file

@ -0,0 +1,19 @@
import os
@_exported import OSLog
extension Logger {
private static let loggers: OSAllocatedUnfairLock<[String: Logger]> = OSAllocatedUnfairLock(initialState: [:])
public static let subsystem = Constants.bundleIdentifier
public static func logger(for type: Any.Type) -> Logger {
let category = String(describing: type)
let logger = loggers.withLock { loggers in
if let logger = loggers[category] { return logger }
let logger = Logger(subsystem: subsystem, category: category)
loggers[category] = logger
return logger
}
return logger
}
}

View file

@ -0,0 +1,5 @@
PRODUCT_NAME = BurrowShared
MERGEABLE_LIBRARY = YES
SWIFT_INCLUDE_PATHS = $(PROJECT_DIR)/Shared/Constants
GCC_PREPROCESSOR_DEFINITIONS = APP_BUNDLE_IDENTIFIER=$(APP_BUNDLE_IDENTIFIER) APP_GROUP_IDENTIFIER=$(APP_GROUP_IDENTIFIER)