Compare commits

..

No commits in common. "f34fb10922ef27dcb7d273b05e2071a6cb95ae82" and "9e03c9680cfa9818b46d85f01c4506837d4de452" have entirely different histories.

40 changed files with 1161 additions and 1352 deletions

View file

@ -13,6 +13,5 @@
], ],
"[rust]": { "[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer", "editor.defaultFormatter": "rust-lang.rust-analyzer",
}, }
"rust-analyzer.inlayHints.typeHints.enable": false
} }

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
SKIP_INSTALL = NO SKIP_INSTALL = NO
MERGED_BINARY_TYPE = manual
LD_RUNPATH_SEARCH_PATHS[sdk=iphone*] = $(inherited) @executable_path/Frameworks LD_RUNPATH_SEARCH_PATHS[sdk=iphone*] = $(inherited) @executable_path/Frameworks
LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(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 INFOPLIST_FILE = Configuration/Info.plist
GENERATE_INFOPLIST_FILE = YES GENERATE_INFOPLIST_FILE = YES
INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2023-2024 Hack Club INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2023 Hack Club
INFOPLIST_KEY_CFBundleDisplayName = Burrow INFOPLIST_KEY_CFBundleDisplayName = Burrow
ENABLE_BITCODE = NO ENABLE_BITCODE = NO

View file

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

View file

@ -0,0 +1,133 @@
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

@ -1,60 +0,0 @@
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,11 +8,10 @@ enum BurrowError: Error {
case resultIsNone case resultIsNone
} }
protocol Request: Codable where Command: Codable { protocol Request: Codable where CommandT: Codable {
associatedtype Command associatedtype CommandT
var id: UInt { get set } var id: UInt { get set }
var command: Command { get set } var command: CommandT { get set }
} }
struct BurrowSingleCommand: Request { struct BurrowSingleCommand: Request {
@ -39,6 +38,13 @@ struct BurrowStartRequest: Codable {
let Start: StartOptions 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 { struct Response<T>: Decodable where T: Decodable {
var id: UInt var id: UInt
var result: T var result: T

View file

@ -1,32 +0,0 @@
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,19 +2,17 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<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> <key>com.apple.security.network.client</key>
<true/> <true/>
<key>com.apple.security.network.server</key> <key>com.apple.security.network.server</key>
<true/> <true/>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>$(APP_GROUP_IDENTIFIER)</string>
</array>
</dict> </dict>
</plist> </plist>

View file

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

View file

@ -1,54 +0,0 @@
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,63 +1,69 @@
import BurrowShared
import libburrow import libburrow
import NetworkExtension import NetworkExtension
import os import os
class PacketTunnelProvider: NEPacketTunnelProvider { class PacketTunnelProvider: NEPacketTunnelProvider {
private let logger = Logger.logger(for: PacketTunnelProvider.self) let logger = Logger(subsystem: "com.hackclub.burrow", category: "frontend")
var client: BurrowIpc?
var osInitialized = false
override func startTunnel(options: [String: NSObject]? = nil) async throws { 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 { do {
libburrow.spawnInProcess(socketPath: try Constants.socketURL.path) let command = BurrowSingleCommand(id: 0, command: "ServerConfig")
guard let data = try await client?.request(command, type: Response<BurrowResult<ServerConfigData>>.self)
let client = try Client() else {
throw BurrowError.cantParseResult
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) let encoded = try JSONEncoder().encode(data.result)
self.logger.log("Received final data: \(String(decoding: encoded, as: UTF8.self))") self.logger.log("Received final data: \(String(decoding: encoded, as: UTF8.self))")
guard let serverconfig = data.result.Ok else { guard let serverconfig = data.result.Ok else {
throw BurrowError.resultIsError throw BurrowError.resultIsError
} }
guard let tunNs = generateTunSettings(from: serverconfig) else { guard let tunNs = self.generateTunSettings(from: serverconfig) else {
throw BurrowError.addrDoesntExist throw BurrowError.addrDoesntExist
} }
try await self.setTunnelNetworkSettings(tunNs) try await self.setTunnelNetworkSettings(tunNs)
self.logger.info("Set remote tunnel address to \(tunNs.tunnelRemoteAddress)") self.logger.info("Set remote tunnel address to \(tunNs.tunnelRemoteAddress)")
let startRequest = BurrowRequest( // let tunFd = self.packetFlow.value(forKeyPath: "socket.fileDescriptor") as! Int;
id: .random(in: (.min)..<(.max)), // self.logger.info("Found File Descriptor: \(tunFd)")
command: BurrowStartRequest( let startCommand = start_req_fd(id: 1)
Start: BurrowStartRequest.StartOptions( guard let data = try await client?.request(startCommand, type: Response<BurrowResult<String>>.self)
tun: BurrowStartRequest.TunOptions( else {
name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: nil throw BurrowError.cantParseResult
) }
) let encodedStartRes = try JSONEncoder().encode(data.result)
) self.logger.log("Received start server response: \(String(decoding: encodedStartRes, as: UTF8.self))")
)
let response = try await client.request(startRequest, type: Response<BurrowResult<String>>.self)
self.logger.log("Received start server response: \(String(describing: response.result))")
} catch { } catch {
self.logger.error("Failed to start tunnel: \(error)") self.logger.error("An error occurred: \(error)")
throw error throw error
} }
} }
private func generateTunSettings(from: ServerConfigData) -> NETunnelNetworkSettings? { private func generateTunSettings(from: ServerConfigData) -> NETunnelNetworkSettings? {
let cfig = from.ServerConfig let cfig = from.ServerConfig
guard let addr = cfig.address else { guard let addr = cfig.address else {
return nil return nil
} }
let settings = NEIPv4Settings(addresses: [addr], subnetMasks: ["255.255.255.0"])
settings.includedRoutes = [.default()]
// Using a makeshift remote tunnel address // Using a makeshift remote tunnel address
let nst = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1") let nst = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
nst.ipv4Settings = settings nst.ipv4Settings = NEIPv4Settings(addresses: [addr], subnetMasks: ["255.255.255.0"])
nst.dnsSettings = .init(servers: ["1.1.1.1"])
logger.log("Initialized ipv4 settings: \(nst.ipv4Settings)") logger.log("Initialized ipv4 settings: \(nst.ipv4Settings)")
return nst 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 @@
__attribute__((__swift_name__("spawnInProcess(socketPath:)"))) void start_srv();
extern void spawn_in_process(const char * __nullable path); void initialize_oslog();

View file

@ -1,22 +0,0 @@
@_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

@ -1,11 +0,0 @@
#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

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

View file

@ -1,19 +0,0 @@
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

@ -1,5 +0,0 @@
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)

1036
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -12,44 +12,46 @@ crate-type = ["lib", "staticlib"]
anyhow = "1.0" anyhow = "1.0"
tokio = { version = "1.21", features = ["rt", "macros", "sync", "io-util", "rt-multi-thread", "time", "tracing"] } tokio = { version = "1.21", features = ["rt", "macros", "sync", "io-util", "rt-multi-thread", "time", "tracing"] }
tun = { version = "0.1", path = "../tun", features = ["serde", "tokio"] } tun = { version = "0.1", path = "../tun", features = ["serde", "tokio"] }
clap = { version = "4.4", features = ["derive"] } clap = { version = "4.3.2", features = ["derive"] }
tracing = "0.1" tracing = "0.1"
tracing-log = "0.1" tracing-log = "0.1"
tracing-journald = "0.3"
tracing-oslog = {git = "https://github.com/Stormshield-robinc/tracing-oslog"} tracing-oslog = {git = "https://github.com/Stormshield-robinc/tracing-oslog"}
tracing-subscriber = { version = "0.3" , features = ["std", "env-filter"]} tracing-subscriber = { version = "0.3" , features = ["std", "env-filter"]}
env_logger = "0.10"
log = "0.4" log = "0.4"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1.0" serde_json = "1"
blake2 = "0.10" blake2 = "0.10.6"
chacha20poly1305 = "0.10" chacha20poly1305 = "0.10.1"
rand = "0.8" rand = "0.8.5"
rand_core = "0.6" rand_core = "0.6.4"
aead = "0.5" aead = "0.5.2"
x25519-dalek = { version = "2.0", features = ["reusable_secrets", "static_secrets"] } x25519-dalek = { version = "2.0.0", features = ["reusable_secrets", "static_secrets"] }
ring = "0.17" ring = "0.17.7"
parking_lot = "0.12" parking_lot = "0.12.1"
hmac = "0.12" hmac = "0.12"
base64 = "0.21" ipnet = { version = "2.8.0", features = ["serde"] }
fehler = "1.0" base64 = "0.21.4"
ip_network_table = "0.2" fehler = "1.0.0"
ip_network = "0.4" ip_network_table = "0.2.0"
async-channel = "2.1" ip_network = "0.4.0"
async-channel = "2.1.1"
schemars = "0.8" schemars = "0.8"
futures = "0.3.28" futures = "0.3.28"
once_cell = "1.19" uuid = { version = "1.6.1", features = ["v4"] }
console-subscriber = { version = "0.2.0" , optional = true} console-subscriber = { version = "0.2.0" , optional = true}
console = "0.15.8"
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
caps = "0.5" caps = "0.5.5"
libsystemd = "0.7" libsystemd = "0.6"
tracing-journald = "0.3"
[target.'cfg(target_vendor = "apple")'.dependencies] [target.'cfg(target_vendor = "apple")'.dependencies]
nix = { version = "0.27" } nix = { version = "0.26.2" }
[dev-dependencies] [dev-dependencies]
insta = { version = "1.32", features = ["yaml"] } insta = { version = "1.32.0", features = ["yaml"] }
etherparse = "0.12"
[package.metadata.generate-rpm] [package.metadata.generate-rpm]
assets = [ assets = [

13
burrow/src/apple.rs Normal file
View file

@ -0,0 +1,13 @@
use tracing::debug;
use tracing_oslog::OsLogger;
use tracing_subscriber::layer::SubscriberExt;
pub use crate::daemon::start_srv;
#[no_mangle]
pub extern "C" fn initialize_oslog() {
let collector =
tracing_subscriber::registry().with(OsLogger::new("com.hackclub.burrow", "backend"));
tracing::subscriber::set_global_default(collector).unwrap();
debug!("Initialized oslog tracing in libburrow rust FFI");
}

View file

@ -1,55 +0,0 @@
use std::{
ffi::{c_char, CStr},
path::PathBuf,
sync::Arc,
thread,
};
use once_cell::sync::OnceCell;
use tokio::{
runtime::{Builder, Handle},
sync::Notify,
};
use tracing::error;
use crate::daemon::daemon_main;
static BURROW_NOTIFY: OnceCell<Arc<Notify>> = OnceCell::new();
static BURROW_HANDLE: OnceCell<Handle> = OnceCell::new();
#[no_mangle]
pub unsafe extern "C" fn spawn_in_process(path: *const c_char) {
crate::tracing::initialize();
let notify = BURROW_NOTIFY.get_or_init(|| Arc::new(Notify::new()));
let handle = BURROW_HANDLE.get_or_init(|| {
let path_buf = if path.is_null() {
None
} else {
Some(PathBuf::from(CStr::from_ptr(path).to_str().unwrap()))
};
let sender = notify.clone();
let (handle_tx, handle_rx) = tokio::sync::oneshot::channel();
thread::spawn(move || {
let runtime = Builder::new_multi_thread()
.worker_threads(4)
.enable_all()
.thread_name("burrow-worker")
.build()
.unwrap();
handle_tx.send(runtime.handle().clone()).unwrap();
runtime.block_on(async {
let result = daemon_main(path_buf.as_deref(), Some(sender.clone())).await;
if let Err(error) = result.as_ref() {
error!("Burrow thread exited: {}", error);
}
result
})
});
handle_rx.blocking_recv().unwrap()
});
let receiver = notify.clone();
handle.block_on(async move { receiver.notified().await });
}

View file

@ -1,6 +1,5 @@
use std::{path::Path, sync::Arc}; use std::sync::Arc;
pub mod apple;
mod command; mod command;
mod instance; mod instance;
mod net; mod net;
@ -9,52 +8,44 @@ mod response;
use anyhow::Result; use anyhow::Result;
pub use command::{DaemonCommand, DaemonStartOptions}; pub use command::{DaemonCommand, DaemonStartOptions};
use instance::DaemonInstance; use instance::DaemonInstance;
pub use net::{DaemonClient, Listener}; #[cfg(target_vendor = "apple")]
pub use net::start_srv;
pub use net::DaemonClient;
pub use response::{DaemonResponse, DaemonResponseData, ServerInfo}; pub use response::{DaemonResponse, DaemonResponseData, ServerInfo};
use tokio::sync::{Notify, RwLock}; use tokio::sync::{Notify, RwLock};
use tracing::{error, info};
use crate::wireguard::{Config, Interface}; use crate::{
daemon::net::listen,
wireguard::{Config, Interface},
};
pub async fn daemon_main(path: Option<&Path>, notify_ready: Option<Arc<Notify>>) -> Result<()> { pub async fn daemon_main(notify_ready: Option<Arc<Notify>>) -> Result<()> {
let (commands_tx, commands_rx) = async_channel::unbounded(); let (commands_tx, commands_rx) = async_channel::unbounded();
let (response_tx, response_rx) = async_channel::unbounded(); let (response_tx, response_rx) = async_channel::unbounded();
let listener = if let Some(path) = path {
info!("Creating listener... {:?}", path);
Listener::new_with_path(commands_tx, response_rx, path)
} else {
info!("Creating listener...");
Listener::new(commands_tx, response_rx)
};
if let Some(n) = notify_ready {
n.notify_one()
}
let listener = listener?;
let config = Config::default(); let config = Config::default();
let iface: Interface = config.try_into()?; let iface: Interface = config.try_into()?;
let mut instance = DaemonInstance::new(commands_rx, response_tx, Arc::new(RwLock::new(iface)));
info!("Starting daemon..."); let mut inst: DaemonInstance =
DaemonInstance::new(commands_rx, response_tx, Arc::new(RwLock::new(iface)));
let main_job = tokio::spawn(async move { tracing::info!("Starting daemon jobs...");
let result = instance.run().await;
if let Err(e) = result.as_ref() { let inst_job = tokio::spawn(async move {
error!("Instance exited: {}", e); let res = inst.run().await;
if let Err(e) = res {
tracing::error!("Error when running instance: {}", e);
} }
result
}); });
let listener_job = tokio::spawn(async move { let listen_job = tokio::spawn(async move {
let result = listener.run().await; let res = listen(commands_tx, response_rx, notify_ready).await;
if let Err(e) = result.as_ref() { if let Err(e) = res {
error!("Listener exited: {}", e); tracing::error!("Error when listening: {}", e);
} }
result
}); });
tokio::try_join!(main_job, listener_job) tokio::try_join!(inst_job, listen_job)
.map(|_| ()) .map(|_| ())
.map_err(|e| e.into()) .map_err(|e| e.into())
} }

View file

@ -0,0 +1,32 @@
use std::sync::Arc;
use std::thread;
use tokio::runtime::Runtime;
use tokio::sync::Notify;
use tracing::{error, info};
use crate::daemon::{daemon_main, DaemonClient};
#[no_mangle]
pub extern "C" fn start_srv() {
info!("Starting server");
let start_notify = Arc::new(Notify::new());
let start_recv = start_notify.clone();
let _handle = thread::spawn(move || {
let rt = Runtime::new().unwrap();
rt.block_on(async {
if let Err(e) = daemon_main(Some(start_notify.clone())).await {
error!("Error when starting rpc server: {}", e);
}
});
start_notify.notify_one();
});
let rt = Runtime::new().unwrap();
rt.block_on(async {
start_recv.notified().await;
match DaemonClient::new().await {
Ok(..) => info!("Server successfully started"),
Err(e) => error!("Could not connect to server: {}", e)
}
});
}

View file

@ -4,18 +4,28 @@ use super::DaemonCommand;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
mod unix; mod unix;
#[cfg(all(target_family = "unix", not(target_os = "linux")))]
pub use unix::{listen, DaemonClient};
#[cfg(target_family = "unix")] #[cfg(target_os = "linux")]
pub use unix::{DaemonClient, Listener}; mod systemd;
#[cfg(target_os = "linux")]
pub use systemd::{listen, DaemonClient};
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod windows; mod windows;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use windows::{DaemonClient, Listener}; pub use windows::{listen, DaemonClient};
#[cfg(target_vendor = "apple")]
mod apple;
#[cfg(target_vendor = "apple")]
pub use apple::start_srv;
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct DaemonRequest { pub struct DaemonRequest {
pub id: u64, pub id: u32,
pub command: DaemonCommand, pub command: DaemonCommand,
} }

View file

@ -0,0 +1,33 @@
use std::os::fd::IntoRawFd;
use std::sync::Arc;
use anyhow::Result;
use tokio::sync::Notify;
use super::*;
use crate::daemon::DaemonResponse;
pub async fn listen(
cmd_tx: async_channel::Sender<DaemonCommand>,
rsp_rx: async_channel::Receiver<DaemonResponse>,
notify: Option<Arc<Notify>>
) -> Result<()> {
if !libsystemd::daemon::booted()
|| listen_with_systemd(cmd_tx.clone(), rsp_rx.clone())
.await
.is_err()
{
unix::listen(cmd_tx, rsp_rx, notify).await?;
}
Ok(())
}
async fn listen_with_systemd(
cmd_tx: async_channel::Sender<DaemonCommand>,
rsp_rx: async_channel::Receiver<DaemonResponse>,
) -> Result<()> {
let fds = libsystemd::activation::receive_descriptors(false)?;
super::unix::listen_with_optional_fd(cmd_tx, rsp_rx, Some(fds[0].clone().into_raw_fd()), None).await
}
pub type DaemonClient = unix::DaemonClient;

View file

@ -1,15 +1,21 @@
#[cfg(target_os = "linux")] use std::{
use std::os::fd::{IntoRawFd, RawFd}; io,
use std::{ffi::OsStr, io, path::Path}; os::{
fd::{FromRawFd, RawFd},
unix::net::UnixListener as StdUnixListener,
},
path::{Path, PathBuf},
};
use std::sync::Arc;
use anyhow::{anyhow, Error, Result}; use anyhow::{anyhow, Result};
use fehler::throws;
use tokio::{ use tokio::{
io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
net::{UnixListener, UnixStream}, net::{UnixListener, UnixStream},
}; };
use tracing::{debug, error, info}; use tracing::{debug, info};
use tokio::sync::Notify;
use super::*; use super::*;
use crate::daemon::{DaemonCommand, DaemonResponse, DaemonResponseData}; use crate::daemon::{DaemonCommand, DaemonResponse, DaemonResponseData};
@ -19,75 +25,93 @@ const UNIX_SOCKET_PATH: &str = "/run/burrow.sock";
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
const UNIX_SOCKET_PATH: &str = "burrow.sock"; const UNIX_SOCKET_PATH: &str = "burrow.sock";
#[derive(Debug)] #[cfg(target_os = "macos")]
pub struct Listener { fn fetch_socket_path() -> Option<PathBuf> {
cmd_tx: async_channel::Sender<DaemonCommand>, let tries = vec![
rsp_rx: async_channel::Receiver<DaemonResponse>, "burrow.sock".to_string(),
inner: UnixListener, format!(
"{}/Library/Containers/com.hackclub.burrow.network/Data/burrow.sock",
std::env::var("HOME").unwrap_or_default()
)
.to_string(),
];
for path in tries {
let path = PathBuf::from(path);
if path.exists() {
return Some(path)
}
}
None
} }
impl Listener { #[cfg(not(target_os = "macos"))]
#[throws] fn fetch_socket_path() -> Option<PathBuf> {
pub fn new( Some(Path::new(UNIX_SOCKET_PATH).to_path_buf())
cmd_tx: async_channel::Sender<DaemonCommand>,
rsp_rx: async_channel::Receiver<DaemonResponse>,
) -> Self {
let path = Path::new(OsStr::new(UNIX_SOCKET_PATH));
Self::new_with_path(cmd_tx, rsp_rx, path)?
} }
#[throws] pub async fn listen(
#[cfg(target_os = "linux")]
pub fn new_with_path(
cmd_tx: async_channel::Sender<DaemonCommand>, cmd_tx: async_channel::Sender<DaemonCommand>,
rsp_rx: async_channel::Receiver<DaemonResponse>, rsp_rx: async_channel::Receiver<DaemonResponse>,
path: &Path, notify: Option<Arc<Notify>>
) -> Self { ) -> Result<()> {
let inner = listener_from_path_or_fd(&path, raw_fd())?; listen_with_optional_fd(cmd_tx, rsp_rx, None, notify).await
Self { cmd_tx, rsp_rx, inner }
} }
#[throws] pub(crate) async fn listen_with_optional_fd(
#[cfg(not(target_os = "linux"))]
pub fn new_with_path(
cmd_tx: async_channel::Sender<DaemonCommand>, cmd_tx: async_channel::Sender<DaemonCommand>,
rsp_rx: async_channel::Receiver<DaemonResponse>, rsp_rx: async_channel::Receiver<DaemonResponse>,
path: &Path, raw_fd: Option<RawFd>,
) -> Self { notify: Option<Arc<Notify>>
let inner = listener_from_path(path)?; ) -> Result<()> {
Self { cmd_tx, rsp_rx, inner } let path = Path::new(UNIX_SOCKET_PATH);
}
pub async fn run(&self) -> Result<()> { let listener = if let Some(raw_fd) = raw_fd {
info!("Waiting for connections..."); let listener = unsafe { StdUnixListener::from_raw_fd(raw_fd) };
listener.set_nonblocking(true)?;
UnixListener::from_std(listener)
} else {
UnixListener::bind(path)
};
let listener = if let Ok(listener) = listener {
listener
} else {
// Won't help all that much, if we use the async version of fs.
if let Some(par) = path.parent() {
std::fs::create_dir_all(par)?;
}
match std::fs::remove_file(path) {
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
stuff => stuff,
}?;
info!("Relative path: {}", path.to_string_lossy());
UnixListener::bind(path)?
};
if let Some(notify) = notify {
notify.notify_one();
}
loop { loop {
let (stream, _) = self.inner.accept().await?; let (stream, _) = listener.accept().await?;
let cmd_tx = self.cmd_tx.clone(); let cmd_tx = cmd_tx.clone();
let rsp_rxc = self.rsp_rx.clone();
tokio::task::spawn(async move {
info!("Got connection: {:?}", stream);
Self::stream(stream, cmd_tx, rsp_rxc).await;
});
}
}
async fn stream( // I'm pretty sure we won't need to manually join / shut this down,
stream: UnixStream, // `lines` will return Err during dropping, and this task should exit
cmd_tx: async_channel::Sender<DaemonCommand>, // gracefully.
rsp_rxc: async_channel::Receiver<DaemonResponse>, let rsp_rxc = rsp_rx.clone();
) { tokio::task::spawn(async move {
let cmd_tx = cmd_tx;
let mut stream = stream; let mut stream = stream;
let (mut read_stream, mut write_stream) = stream.split(); let (mut read_stream, mut write_stream) = stream.split();
let buf_reader = BufReader::new(&mut read_stream); let buf_reader = BufReader::new(&mut read_stream);
let mut lines = buf_reader.lines(); let mut lines = buf_reader.lines();
while let Ok(Some(line)) = lines.next_line().await { while let Ok(Some(line)) = lines.next_line().await {
info!("Line: {}", line); info!("Got line: {}", line);
debug!("Line raw data: {:?}", line.as_bytes());
let mut res: DaemonResponse = DaemonResponseData::None.into(); let mut res: DaemonResponse = DaemonResponseData::None.into();
let req = match serde_json::from_str::<DaemonRequest>(&line) { let req = match serde_json::from_str::<DaemonRequest>(&line) {
Ok(req) => Some(req), Ok(req) => Some(req),
Err(e) => { Err(e) => {
res.result = Err(e.to_string()); res.result = Err(e.to_string());
error!("Failed to parse request: {}", e); tracing::error!("Failed to parse request: {}", e);
None None
} }
}; };
@ -105,92 +129,37 @@ impl Listener {
write_stream.write_all(res.as_bytes()).await.unwrap(); write_stream.write_all(res.as_bytes()).await.unwrap();
} }
} }
});
} }
} }
#[cfg(target_os = "linux")]
fn raw_fd() -> Option<RawFd> {
if !libsystemd::daemon::booted() {
return None;
}
match libsystemd::activation::receive_descriptors(false) {
Ok(descriptors) => descriptors.into_iter().map(|d| d.into_raw_fd()).next(),
Err(e) => {
tracing::error!("Failed to receive descriptors: {}", e);
None
}
}
}
#[throws]
#[cfg(target_os = "linux")]
fn listener_from_path_or_fd(path: &Path, raw_fd: Option<RawFd>) -> UnixListener {
match raw_fd.map(listener_from_fd) {
Some(Ok(listener)) => listener,
_ => listener_from_path(path)?,
}
}
#[throws]
#[cfg(target_os = "linux")]
fn listener_from_fd(fd: RawFd) -> UnixListener {
use std::os::fd::FromRawFd;
let listener = unsafe { std::os::unix::net::UnixListener::from_raw_fd(fd) };
listener.set_nonblocking(true)?;
UnixListener::from_std(listener)?
}
#[throws]
fn listener_from_path(path: &Path) -> UnixListener {
let error = match UnixListener::bind(path) {
Ok(listener) => return listener,
Err(e) => e,
};
match error.kind() {
io::ErrorKind::NotFound => {
if let Some(parent) = path.parent() {
info!("Creating parent directory {:?}", parent);
std::fs::create_dir_all(parent)?;
}
}
io::ErrorKind::AddrInUse => {
info!("Removing existing file");
match std::fs::remove_file(path) {
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
stuff => stuff,
}?;
}
_ => error!("Failed to bind to {:?}: {}", path, error),
}
UnixListener::bind(path)?
}
#[derive(Debug)] #[derive(Debug)]
pub struct DaemonClient { pub struct DaemonClient {
stream: UnixStream, connection: UnixStream,
} }
impl DaemonClient { impl DaemonClient {
pub async fn new() -> Result<Self> { pub async fn new() -> Result<Self> {
let path = Path::new(OsStr::new(UNIX_SOCKET_PATH)); let path = fetch_socket_path().ok_or(anyhow!("Failed to find socket path"))?;
Self::new_with_path(path).await // debug!("found path: {:?}", path);
let connection = UnixStream::connect(path).await?;
debug!("connected to socket");
Ok(Self { connection })
} }
pub async fn new_with_path(path: &Path) -> Result<Self> { pub async fn new_with_path(path: &str) -> Result<Self> {
let stream = UnixStream::connect(path).await?; let path = Path::new(path);
Ok(Self { stream }) let connection = UnixStream::connect(path).await?;
Ok(Self { connection })
} }
pub async fn send_command(&mut self, command: DaemonCommand) -> Result<DaemonResponse> { pub async fn send_command(&mut self, command: DaemonCommand) -> Result<DaemonResponse> {
let mut command = serde_json::to_string(&DaemonRequest { id: 0, command })?; let mut command = serde_json::to_string(&DaemonRequest { id: 0, command })?;
command.push('\n'); command.push('\n');
self.stream.write_all(command.as_bytes()).await?; self.connection.write_all(command.as_bytes()).await?;
let buf_reader = BufReader::new(&mut self.stream); let buf_reader = BufReader::new(&mut self.connection);
let mut lines = buf_reader.lines(); let mut lines = buf_reader.lines();
let response = lines let response = lines
.next_line() .next_line()

View file

@ -1,34 +1,23 @@
use anyhow::Result; use anyhow::Result;
use fehler::throws;
use super::DaemonCommand; use super::*;
use crate::daemon::DaemonResponse; use crate::daemon::DaemonResponse;
pub struct Listener; pub async fn listen(
_cmd_tx: async_channel::Sender<DaemonCommand>,
impl Listener { _rsp_rx: async_channel::Receiver<DaemonResponse>,
pub fn new_with_path( ) -> Result<()> {
cmd_tx: async_channel::Sender<DaemonCommand>, unimplemented!("This platform does not currently support daemon mode.")
rsp_rx: async_channel::Receiver<DaemonResponse>,
path: &Path,
) -> Self {
Self
} }
pub async fn run(&self) -> Result<()> {
Ok(())
}
}
#[derive(Debug)]
pub struct DaemonClient; pub struct DaemonClient;
impl DaemonClient { impl DaemonClient {
pub async fn new() -> Result<Self> { pub async fn new() -> Result<Self> {
Ok(Self) unimplemented!("This platform does not currently support daemon mode.")
} }
pub async fn send_command(&mut self, command: DaemonCommand) -> Result<DaemonResponse> { pub async fn send_command(&mut self, _: DaemonCommand) -> Result<()> {
unimplemented!("This platform does not currently support daemon mode.") unimplemented!("This platform does not currently support daemon mode.")
} }
} }

View file

@ -6,7 +6,7 @@ use tun::TunInterface;
pub struct DaemonResponse { pub struct DaemonResponse {
// Error types can't be serialized, so this is the second best option. // Error types can't be serialized, so this is the second best option.
pub result: Result<DaemonResponseData, String>, pub result: Result<DaemonResponseData, String>,
pub id: u64, pub id: u32,
} }
impl DaemonResponse { impl DaemonResponse {
@ -25,7 +25,7 @@ impl From<DaemonResponseData> for DaemonResponse {
} }
impl DaemonResponse { impl DaemonResponse {
pub fn with_id(self, id: u64) -> Self { pub fn with_id(self, id: u32) -> Self {
Self { id, ..self } Self { id, ..self }
} }
} }

View file

@ -3,10 +3,6 @@ pub mod wireguard;
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
mod daemon; mod daemon;
pub(crate) mod tracing;
#[cfg(target_vendor = "apple")]
pub use daemon::apple::spawn_in_process;
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
pub use daemon::{ pub use daemon::{
DaemonClient, DaemonClient,
@ -16,3 +12,9 @@ pub use daemon::{
DaemonStartOptions, DaemonStartOptions,
ServerInfo, ServerInfo,
}; };
#[cfg(target_vendor = "apple")]
mod apple;
#[cfg(target_vendor = "apple")]
pub use apple::*;

View file

@ -1,9 +1,14 @@
use anyhow::Result; use anyhow::{Context, Result};
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use tracing::instrument;
use tracing_log::LogTracer;
use tracing_oslog::OsLogger;
use tracing_subscriber::{prelude::*, EnvFilter, FmtSubscriber};
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
use tun::TunInterface;
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
mod daemon; mod daemon;
pub(crate) mod tracing;
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
mod wireguard; mod wireguard;
@ -34,6 +39,8 @@ struct Cli {
enum Commands { enum Commands {
/// Start Burrow /// Start Burrow
Start(StartArgs), Start(StartArgs),
/// Retrieve the file descriptor of the tun interface
Retrieve(RetrieveArgs),
/// Stop Burrow daemon /// Stop Burrow daemon
Stop, Stop,
/// Start Burrow daemon /// Start Burrow daemon
@ -47,6 +54,9 @@ enum Commands {
#[derive(Args)] #[derive(Args)]
struct StartArgs {} struct StartArgs {}
#[derive(Args)]
struct RetrieveArgs {}
#[derive(Args)] #[derive(Args)]
struct DaemonArgs {} struct DaemonArgs {}
@ -61,6 +71,57 @@ async fn try_start() -> Result<()> {
.map(|_| ()) .map(|_| ())
} }
#[cfg(target_vendor = "apple")]
#[instrument]
async fn try_retrieve() -> Result<()> {
LogTracer::init()
.context("Failed to initialize LogTracer")
.unwrap();
if cfg!(target_os = "linux") || cfg!(target_vendor = "apple") {
let maybe_layer = system_log().unwrap();
if let Some(layer) = maybe_layer {
let logger = layer.with_subscriber(FmtSubscriber::new());
tracing::subscriber::set_global_default(logger)
.context("Failed to set the global tracing subscriber")
.unwrap();
}
}
let iface2 = TunInterface::retrieve().ok_or(anyhow::anyhow!("No interface found"))?;
tracing::info!("{:?}", iface2);
Ok(())
}
async fn initialize_tracing() -> Result<()> {
LogTracer::init().context("Failed to initialize LogTracer")?;
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
{
let maybe_layer = system_log()?;
if let Some(layer) = maybe_layer {
let registry = tracing_subscriber::registry()
.with(layer)
.with(tracing_subscriber::fmt::layer()
.with_line_number(true)
.with_filter(EnvFilter::from_default_env())
);
#[cfg(feature = "tokio-console")]
let registry = registry.with(
console_subscriber::spawn()
.with_filter(EnvFilter::from_default_env()
.add_directive("tokio=trace".parse()?)
.add_directive("runtime=trace".parse()?)
)
);
tracing::subscriber::set_global_default(registry).context("Failed to set the global tracing subscriber")?;
}
}
Ok(())
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
async fn try_stop() -> Result<()> { async fn try_stop() -> Result<()> {
let mut client = DaemonClient::new().await?; let mut client = DaemonClient::new().await?;
@ -115,6 +176,11 @@ async fn try_start() -> Result<()> {
Ok(()) Ok(())
} }
#[cfg(not(target_vendor = "apple"))]
async fn try_retrieve() -> Result<()> {
Ok(())
}
#[cfg(not(any(target_os = "linux", target_vendor = "apple")))] #[cfg(not(any(target_os = "linux", target_vendor = "apple")))]
async fn try_stop() -> Result<()> { async fn try_stop() -> Result<()> {
Ok(()) Ok(())
@ -129,17 +195,26 @@ async fn try_serverinfo() -> Result<()> {
async fn try_serverconfig() -> Result<()> { async fn try_serverconfig() -> Result<()> {
Ok(()) Ok(())
} }
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> { async fn main() -> Result<()> {
tracing::initialize(); initialize_tracing().await?;
tracing::info!("Platform: {}", std::env::consts::OS);
let cli = Cli::parse(); let cli = Cli::parse();
match &cli.command { match &cli.command {
Commands::Start(..) => try_start().await?, Commands::Start(..) => {
Commands::Stop => try_stop().await?, try_start().await?;
Commands::Daemon(_) => daemon::daemon_main(None, None).await?, tracing::info!("FINISHED");
}
Commands::Retrieve(..) => {
try_retrieve().await?;
tracing::info!("FINISHED");
}
Commands::Stop => {
try_stop().await?;
}
Commands::Daemon(_) => daemon::daemon_main(None).await?,
Commands::ServerInfo => try_serverinfo().await?, Commands::ServerInfo => try_serverinfo().await?,
Commands::ServerConfig => try_serverconfig().await?, Commands::ServerConfig => try_serverconfig().await?,
} }
@ -147,6 +222,23 @@ async fn main() -> Result<()> {
Ok(()) Ok(())
} }
#[cfg(target_os = "linux")]
fn system_log() -> Result<Option<tracing_journald::Layer>> {
let maybe_journald = tracing_journald::layer();
match maybe_journald {
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
tracing::trace!("journald not found");
Ok(None)
}
_ => Ok(Some(maybe_journald?)),
}
}
#[cfg(target_vendor = "apple")]
fn system_log() -> Result<Option<OsLogger>> {
Ok(Some(OsLogger::new("com.hackclub.burrow", "burrow-cli")))
}
#[cfg(not(any(target_os = "linux", target_vendor = "apple")))] #[cfg(not(any(target_os = "linux", target_vendor = "apple")))]
pub fn main() { pub fn main() {
eprintln!("This platform is not supported currently.") eprintln!("This platform is not supported currently.")

View file

@ -1,63 +0,0 @@
use std::sync::Once;
use tracing::{error, info};
use tracing_subscriber::{
layer::{Layer, SubscriberExt},
EnvFilter,
Registry,
};
static TRACING: Once = Once::new();
pub fn initialize() {
TRACING.call_once(|| {
if let Err(e) = tracing_log::LogTracer::init() {
error!("Failed to initialize LogTracer: {}", e);
}
#[cfg(target_os = "windows")]
let system_log = Some(tracing_subscriber::fmt::layer());
#[cfg(target_os = "linux")]
let system_log = match tracing_journald::layer() {
Ok(layer) => Some(layer),
Err(e) => {
if e.kind() != std::io::ErrorKind::NotFound {
error!("Failed to initialize journald: {}", e);
}
None
}
};
#[cfg(target_vendor = "apple")]
let system_log = Some(tracing_oslog::OsLogger::new(
"com.hackclub.burrow",
"tracing",
));
let stderr = (console::user_attended_stderr() || system_log.is_none()).then(|| {
tracing_subscriber::fmt::layer()
.with_level(true)
.with_writer(std::io::stderr)
.compact()
.with_filter(EnvFilter::from_default_env())
});
let subscriber = Registry::default().with(stderr).with(system_log);
#[cfg(feature = "tokio-console")]
let subscriber = subscriber.with(
console_subscriber::spawn().with_filter(
EnvFilter::from_default_env()
.add_directive("tokio=trace".parse().unwrap())
.add_directive("runtime=trace".parse().unwrap()),
),
);
if let Err(e) = tracing::subscriber::set_global_default(subscriber) {
error!("Failed to initialize logging: {}", e);
}
info!("Initialized logging")
});
}

View file

@ -153,13 +153,7 @@ impl Interface {
let mut buf = [0u8; 65535]; let mut buf = [0u8; 65535];
loop { loop {
tokio::time::sleep(tokio::time::Duration::from_millis(250)).await; tokio::time::sleep(tokio::time::Duration::from_millis(250)).await;
match pcb.update_timers(&mut buf).await { pcb.update_timers(&mut buf).await;
Ok(..) => (),
Err(e) => {
error!("Failed to update timers: {}", e);
return
}
}
} }
}; };
@ -173,7 +167,7 @@ impl Interface {
tsks.extend(vec![ tsks.extend(vec![
tokio::spawn(main_tsk), tokio::spawn(main_tsk),
tokio::spawn(update_timers_tsk), tokio::spawn(update_timers_tsk),
tokio::spawn(reset_rate_limiter_tsk), tokio::spawn(reset_rate_limiter_tsk)
]); ]);
debug!("task made.."); debug!("task made..");
} }

View file

@ -8,3 +8,4 @@ pub use config::Config;
pub use iface::Interface; pub use iface::Interface;
pub use pcb::PeerPcb; pub use pcb::PeerPcb;
pub use peer::Peer; pub use peer::Peer;
pub use x25519_dalek::{PublicKey, StaticSecret};

View file

@ -44,7 +44,13 @@ const MAX_QUEUE_DEPTH: usize = 256;
const N_SESSIONS: usize = 8; const N_SESSIONS: usize = 8;
pub mod x25519 { pub mod x25519 {
pub use x25519_dalek::{PublicKey, ReusableSecret, SharedSecret, StaticSecret}; pub use x25519_dalek::{
EphemeralSecret,
PublicKey,
ReusableSecret,
SharedSecret,
StaticSecret,
};
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -1,17 +1,19 @@
use std::{net::SocketAddr, sync::Arc}; use std::{net::SocketAddr, sync::Arc};
use std::time::Duration;
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use fehler::throws; use fehler::throws;
use ip_network::IpNetwork; use ip_network::IpNetwork;
use rand::random; use rand::random;
use tokio::{net::UdpSocket, sync::RwLock, task::JoinHandle}; use tokio::{net::UdpSocket, sync::RwLock, task::JoinHandle, time::timeout};
use tokio::io::AsyncWrite;
use tun::tokio::TunInterface; use tun::tokio::TunInterface;
use crate::wireguard::noise::errors::WireGuardError;
use super::{ use super::{
noise::{TunnResult, Tunnel}, noise::{TunnResult, Tunnel},
Peer, Peer,
}; };
use crate::wireguard::noise::errors::WireGuardError;
#[derive(Debug)] #[derive(Debug)]
pub struct PeerPcb { pub struct PeerPcb {
@ -93,13 +95,7 @@ impl PeerPcb {
TunnResult::WriteToNetwork(packet) => { TunnResult::WriteToNetwork(packet) => {
tracing::debug!("WriteToNetwork: {:?}", packet); tracing::debug!("WriteToNetwork: {:?}", packet);
self.open_if_closed().await?; self.open_if_closed().await?;
self.socket self.socket.read().await.as_ref().unwrap().send(packet).await?;
.read()
.await
.as_ref()
.unwrap()
.send(packet)
.await?;
tracing::debug!("WriteToNetwork done"); tracing::debug!("WriteToNetwork done");
res_dat = &[]; res_dat = &[];
continue continue
@ -147,7 +143,8 @@ impl PeerPcb {
pub async fn update_timers(&self, dst: &mut [u8]) -> Result<(), Error> { pub async fn update_timers(&self, dst: &mut [u8]) -> Result<(), Error> {
match self.tunnel.write().await.update_timers(dst) { match self.tunnel.write().await.update_timers(dst) {
TunnResult::Done => {} TunnResult::Done => {}
TunnResult::Err(WireGuardError::ConnectionExpired) => {} TunnResult::Err(WireGuardError::ConnectionExpired) => {
}
TunnResult::Err(e) => { TunnResult::Err(e) => {
tracing::error!(message = "Update timers error", error = ?e) tracing::error!(message = "Update timers error", error = ?e)
} }

View file

@ -16,6 +16,7 @@ pub mod sys;
use kern_control::SysControlSocket; use kern_control::SysControlSocket;
pub use super::queue::TunQueue;
use super::{ifname_to_string, string_to_ifname}; use super::{ifname_to_string, string_to_ifname};
use crate::TunOptions; use crate::TunOptions;