diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml new file mode 100644 index 0000000..6709edb --- /dev/null +++ b/.github/workflows/release-linux.yml @@ -0,0 +1,34 @@ +name: Release (Linux) +on: + release: + types: + - created +jobs: + appimage: + name: Build AppImage + runs-on: ubuntu-latest + container: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Build AppImage + run: | + docker build -t appimage-builder . -f burrow-gtk/build-aux/Dockerfile + docker create --name temp appimage-builder + docker cp temp:/app/burrow-gtk/build-appimage/Burrow-x86_64.AppImage . + docker rm temp + - name: Get Build Number + id: version + shell: bash + run: | + echo "BUILD_NUMBER=$(Tools/version.sh)" >> $GITHUB_OUTPUT + - name: Attach Artifacts + uses: SierraSoftworks/gh-releases@v1.0.7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + release_tag: builds/${{ steps.version.outputs.BUILD_NUMBER }} + overwrite: "true" + files: | + Burrow-x86_64.AppImage diff --git a/.vscode/settings.json b/.vscode/settings.json index 3c714be..a760137 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,18 +1,19 @@ { - "files.autoSave": "onFocusChange", - "files.defaultLanguage": "rust", - "editor.formatOnPaste": true, - "editor.formatOnSave": true, - "files.trimTrailingWhitespace": true, - "editor.suggest.preview": true, - "editor.acceptSuggestionOnEnter": "on", - "rust-analyzer.restartServerOnConfigChange": true, - "rust-analyzer.cargo.features": "all", - "rust-analyzer.rustfmt.extraArgs": [ - "+nightly" - ], - "[rust]": { - "editor.defaultFormatter": "rust-lang.rust-analyzer", - }, - "rust-analyzer.inlayHints.typeHints.enable": false -} \ No newline at end of file + "files.autoSave": "onFocusChange", + "files.defaultLanguage": "rust", + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "files.trimTrailingWhitespace": true, + "editor.suggest.preview": true, + "editor.acceptSuggestionOnEnter": "on", + "rust-analyzer.restartServerOnConfigChange": true, + "rust-analyzer.cargo.features": "all", + "rust-analyzer.rustfmt.extraArgs": ["+nightly"], + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer" + }, + "rust-analyzer.inlayHints.typeHints.enable": false, + "rust-analyzer.linkedProjects": [ + "./burrow/Cargo.toml" + ] +} diff --git a/Apple/Burrow.xcodeproj/project.pbxproj b/Apple/Burrow.xcodeproj/project.pbxproj index 3ea58b3..a3be02d 100644 --- a/Apple/Burrow.xcodeproj/project.pbxproj +++ b/Apple/Burrow.xcodeproj/project.pbxproj @@ -7,13 +7,13 @@ objects = { /* Begin PBXBuildFile section */ - 0B28F1562ABF463A000D44B0 /* DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B28F1552ABF463A000D44B0 /* DataTypes.swift */; }; - 0B46E8E02AC918CA00BA2A3C /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B46E8DF2AC918CA00BA2A3C /* Client.swift */; }; + 0BA6D73B2BA638D900BD4B55 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B46E8DF2AC918CA00BA2A3C /* Client.swift */; }; + 0BA6D73C2BA6393200BD4B55 /* NWConnection+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */; }; + 0BA6D73D2BA6393B00BD4B55 /* NewlineProtocolFramer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */; }; + 0BA6D73E2BA6394B00BD4B55 /* DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B28F1552ABF463A000D44B0 /* DataTypes.swift */; }; 43AA26D82A10004900F14CE6 /* MenuItemToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */; }; D000363D2BB8928E00E582EC /* NetworkCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363C2BB8928E00E582EC /* NetworkCarouselView.swift */; }; D000363F2BB895FB00E582EC /* OAuth2.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363E2BB895FB00E582EC /* OAuth2.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 */; }; @@ -158,6 +158,10 @@ D00117392B30341C00D87C25 /* Shared */ = { isa = PBXGroup; children = ( + 0B28F1552ABF463A000D44B0 /* DataTypes.swift */, + D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */, + D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */, + 0B46E8DF2AC918CA00BA2A3C /* Client.swift */, D001173A2B30341C00D87C25 /* Logging.swift */, D08252752B5C9FC4005DA378 /* Constants.swift */, D00117422B30348D00D87C25 /* Shared.xcconfig */, @@ -199,10 +203,6 @@ 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 */, @@ -456,7 +456,11 @@ buildActionMask = 2147483647; files = ( D001173B2B30341C00D87C25 /* Logging.swift in Sources */, + 0BA6D73C2BA6393200BD4B55 /* NWConnection+Async.swift in Sources */, D08252762B5C9FC4005DA378 /* Constants.swift in Sources */, + 0BA6D73E2BA6394B00BD4B55 /* DataTypes.swift in Sources */, + 0BA6D73B2BA638D900BD4B55 /* Client.swift in Sources */, + 0BA6D73D2BA6393B00BD4B55 /* NewlineProtocolFramer.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -464,10 +468,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D00117332B3001A400D87C25 /* NewlineProtocolFramer.swift in Sources */, - 0B28F1562ABF463A000D44B0 /* DataTypes.swift in Sources */, - D00117312B2FFFC900D87C25 /* NWConnection+Async.swift in Sources */, - 0B46E8E02AC918CA00BA2A3C /* Client.swift in Sources */, D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Apple/NetworkExtension/Client.swift b/Apple/NetworkExtension/Client.swift deleted file mode 100644 index e7c1bc8..0000000 --- a/Apple/NetworkExtension/Client.swift +++ /dev/null @@ -1,60 +0,0 @@ -import BurrowShared -import Foundation -import Network - -final class Client { - let connection: NWConnection - - private let 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(_ 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) - } - } -} diff --git a/Apple/NetworkExtension/DataTypes.swift b/Apple/NetworkExtension/DataTypes.swift deleted file mode 100644 index 1409fde..0000000 --- a/Apple/NetworkExtension/DataTypes.swift +++ /dev/null @@ -1,61 +0,0 @@ -import Foundation - -// swiftlint:disable identifier_name -enum BurrowError: Error { - case addrDoesntExist - case resultIsError - case cantParseResult - case resultIsNone -} - -protocol Request: Codable where Command: Codable { - associatedtype Command - - var id: UInt { get set } - var command: Command { get set } -} - -struct BurrowSingleCommand: Request { - var id: UInt - var command: String -} - -struct BurrowRequest: Request where T: Codable { - var id: UInt - var command: T -} - -struct BurrowStartRequest: Codable { - struct TunOptions: Codable { - let name: String? - let no_pi: Bool - let tun_excl: Bool - let tun_retrieve: Bool - let address: [String] - } - struct StartOptions: Codable { - let tun: TunOptions - } - let Start: StartOptions -} - -struct Response: Decodable where T: Decodable { - var id: UInt - var result: T -} - -struct BurrowResult: Codable where T: Codable { - var Ok: T? - var Err: String? -} - -struct ServerConfigData: Codable { - struct InternalConfig: Codable { - let address: [String] - let name: String? - let mtu: Int32? - } - let ServerConfig: InternalConfig -} - -// swiftlint:enable identifier_name diff --git a/Apple/NetworkExtension/PacketTunnelProvider.swift b/Apple/NetworkExtension/PacketTunnelProvider.swift index a07daa3..89e0de6 100644 --- a/Apple/NetworkExtension/PacketTunnelProvider.swift +++ b/Apple/NetworkExtension/PacketTunnelProvider.swift @@ -5,10 +5,14 @@ import os class PacketTunnelProvider: NEPacketTunnelProvider { private let logger = Logger.logger(for: PacketTunnelProvider.self) + private var client: Client? override init() { do { - libburrow.spawnInProcess(socketPath: try Constants.socketURL.path) + libburrow.spawnInProcess( + socketPath: try Constants.socketURL.path(percentEncoded: false), + dbPath: try Constants.dbURL.path(percentEncoded: false) + ) } catch { logger.error("Failed to spawn: \(error)") } @@ -17,33 +21,17 @@ class PacketTunnelProvider: NEPacketTunnelProvider { override func startTunnel(options: [String: NSObject]? = nil) async throws { do { let client = try Client() + self.client = client + register_events(client) - let command = BurrowRequest(id: 0, command: "ServerConfig") - let data = try await client.request(command, type: Response>.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 = generateTunSettings(from: serverconfig) else { - throw BurrowError.addrDoesntExist - } - try await self.setTunnelNetworkSettings(tunNs) - self.logger.info("Set remote tunnel address to \(tunNs.tunnelRemoteAddress)") - - 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: [] - ) - ) + _ = try await self.loadTunSettings() + let startRequest = Start( + tun: Start.TunOptions( + name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: [] ) ) - let response = try await client.request(startRequest, type: Response>.self) - self.logger.log("Received start server response: \(String(describing: response.result))") + let response = try await client.request(startRequest, type: BurrowResult.self) + self.logger.log("Received start server response: \(String(describing: response))") } catch { self.logger.error("Failed to start tunnel: \(error)") throw error @@ -53,20 +41,33 @@ class PacketTunnelProvider: NEPacketTunnelProvider { override func stopTunnel(with reason: NEProviderStopReason) async { do { let client = try Client() - let command = BurrowRequest(id: 0, command: "Stop") - let data = try await client.request(command, type: Response>.self) + _ = try await client.single_request("Stop", type: BurrowResult.self) self.logger.log("Stopped client.") } catch { self.logger.error("Failed to stop tunnel: \(error)") } } - - private func generateTunSettings(from: ServerConfigData) -> NETunnelNetworkSettings? { - let cfig = from.ServerConfig + func loadTunSettings() async throws -> ServerConfig { + guard let client = self.client else { + throw BurrowError.noClient + } + let srvConfig = try await client.single_request("ServerConfig", type: BurrowResult.self) + guard let serverconfig = srvConfig.Ok else { + throw BurrowError.resultIsError + } + 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)") + return serverconfig + } + private func generateTunSettings(from: ServerConfig) -> NETunnelNetworkSettings? { + // Using a makeshift remote tunnel address let nst = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1") var v4Addresses = [String]() var v6Addresses = [String]() - for addr in cfig.address { + for addr in from.address { if IPv4Address(addr) != nil { v6Addresses.append(addr) } @@ -81,4 +82,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider { logger.log("Initialized ipv4 settings: \(nst.ipv4Settings)") return nst } + func register_events(_ client: Client) { + client.on_event(.ConfigChange) { (cfig: ServerConfig) in + self.logger.info("Config Change Notification: \(String(describing: cfig))") + self.setTunnelNetworkSettings(self.generateTunSettings(from: cfig)) + self.logger.info("Updated Tunnel Network Settings.") + } + } } diff --git a/Apple/NetworkExtension/libburrow/libburrow.h b/Apple/NetworkExtension/libburrow/libburrow.h index e500de4..2b578ab 100644 --- a/Apple/NetworkExtension/libburrow/libburrow.h +++ b/Apple/NetworkExtension/libburrow/libburrow.h @@ -1,2 +1,2 @@ -__attribute__((__swift_name__("spawnInProcess(socketPath:)"))) -extern void spawn_in_process(const char * __nullable path); +__attribute__((__swift_name__("spawnInProcess(socketPath:dbPath:)"))) +extern void spawn_in_process(const char * __nullable socket_path, const char * __nullable db_path); diff --git a/Apple/Shared/Client.swift b/Apple/Shared/Client.swift new file mode 100644 index 0000000..f643c6c --- /dev/null +++ b/Apple/Shared/Client.swift @@ -0,0 +1,106 @@ +import Foundation +import Network + +public final class Client { + let connection: NWConnection + + private let logger = Logger.logger(for: Client.self) + private var generator = SystemRandomNumberGenerator() + private var continuations: [UInt: UnsafeContinuation] = [:] + private var eventMap: [NotificationType: [(Data) throws -> Void]] = [:] + private var task: Task? + + public convenience init() throws { + self.init(url: try Constants.socketURL) + } + + public 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) + let connection = NWConnection(to: endpoint, using: parameters) + connection.start(queue: .global()) + self.connection = connection + self.task = Task { [weak self] in + while true { + let (data, _, _) = try await connection.receiveMessage() + let peek = try JSONDecoder().decode(MessagePeek.self, from: data) + switch peek.type { + case .Response: + let response = try JSONDecoder().decode(ResponsePeek.self, from: data) + self?.logger.info("Received response for \(response.id)") + guard let continuations = self?.continuations else {return} + self?.logger.debug("All keys in continuation table: \(continuations.keys)") + guard let continuation = self?.continuations[response.id] else { return } + self?.logger.debug("Got matching continuation") + continuation.resume(returning: data) + case .Notification: + let peek = try JSONDecoder().decode(NotificationPeek.self, from: data) + guard let handlers = self?.eventMap[peek.method] else { continue } + _ = try handlers.map { try $0(data) } + default: + continue + } + } + } + } + private func send(_ request: T) async throws -> U { + let data: Data = try await withUnsafeThrowingContinuation { continuation in + continuations[request.id] = continuation + do { + let data = try JSONEncoder().encode(request) + let completion: NWConnection.SendCompletion = .contentProcessed { error in + guard let error = error else { + return + } + continuation.resume(throwing: error) + } + connection.send(content: data, completion: completion) + } catch { + continuation.resume(throwing: error) + return + } + } + self.logger.debug("Got response data: \(String(describing: data.base64EncodedString()))") + let res = try JSONDecoder().decode(Response.self, from: data) + self.logger.debug("Got response data decoded: \(String(describing: res))") + return res.result + } + public func request(_ request: T, type: U.Type = U.self) async throws -> U { + let req = BurrowRequest( + id: generator.next(upperBound: UInt.max), + command: request + ) + return try await send(req) + } + public func single_request(_ request: String, type: U.Type = U.self) async throws -> U { + let req = BurrowSimpleRequest( + id: generator.next(upperBound: UInt.max), + command: request + ) + return try await send(req) + } + public func on_event(_ event: NotificationType, callable: @escaping (T) throws -> Void) { + let action = { data in + let decoded = try JSONDecoder().decode(Notification.self, from: data) + try callable(decoded.params) + } + if eventMap[event] != nil { + eventMap[event]?.append(action) + } else { + eventMap[event] = [action] + } + } + + deinit { + connection.cancel() + } +} diff --git a/Apple/Shared/Constants.swift b/Apple/Shared/Constants.swift index 634c500..a8207cd 100644 --- a/Apple/Shared/Constants.swift +++ b/Apple/Shared/Constants.swift @@ -20,4 +20,14 @@ public enum Constants { } return .success(groupContainerURL) }() + public static var socketURL: URL { + get throws { + try groupContainerURL.appending(component: "burrow.sock", directoryHint: .notDirectory) + } + } + public static var dbURL: URL { + get throws { + try groupContainerURL.appending(component: "burrow.db", directoryHint: .notDirectory) + } + } } diff --git a/Apple/Shared/DataTypes.swift b/Apple/Shared/DataTypes.swift new file mode 100644 index 0000000..ac49abc --- /dev/null +++ b/Apple/Shared/DataTypes.swift @@ -0,0 +1,139 @@ +import Foundation + +// swiftlint:disable identifier_name raw_value_for_camel_cased_codable_enum +public enum BurrowError: Error { + case addrDoesntExist + case resultIsError + case cantParseResult + case resultIsNone + case noClient +} + +public protocol Request: Codable where Params: Codable { + associatedtype Params + + var id: UInt { get set } + var method: String { get set } + var params: Params? { get set } +} + +public enum MessageType: String, Codable { + case Request + case Response + case Notification +} + +public struct MessagePeek: Codable { + public var type: MessageType + public init(type: MessageType) { + self.type = type + } +} + +public struct BurrowSimpleRequest: Request { + public var id: UInt + public var method: String + public var params: String? + public init(id: UInt, command: String, params: String? = nil) { + self.id = id + self.method = command + self.params = params + } +} + +public struct BurrowRequest: Request where T: Codable { + public var id: UInt + public var method: String + public var params: T? + public init(id: UInt, command: T) { + self.id = id + self.method = "\(T.self)" + self.params = command + } +} + +public struct Response: Decodable where T: Decodable { + public var id: UInt + public var result: T + public init(id: UInt, result: T) { + self.id = id + self.result = result + } +} + +public struct ResponsePeek: Codable { + public var id: UInt + public init(id: UInt) { + self.id = id + } +} + +public enum NotificationType: String, Codable { + case ConfigChange +} + +public struct Notification: Codable where T: Codable { + public var method: NotificationType + public var params: T + public init(method: NotificationType, params: T) { + self.method = method + self.params = params + } +} + +public struct NotificationPeek: Codable { + public var method: NotificationType + public init(method: NotificationType) { + self.method = method + } +} + +public struct AnyResponseData: Codable { + public var type: String + public init(type: String) { + self.type = type + } +} + +public struct BurrowResult: Codable where T: Codable { + public var Ok: T? + public var Err: String? + public init(Ok: T, Err: String? = nil) { + self.Ok = Ok + self.Err = Err + } +} + +public struct ServerConfig: Codable { + public let address: [String] + public let name: String? + public let mtu: Int32? + public init(address: [String], name: String?, mtu: Int32?) { + self.address = address + self.name = name + self.mtu = mtu + } +} + +public struct Start: Codable { + public struct TunOptions: Codable { + public let name: String? + public let no_pi: Bool + public let tun_excl: Bool + public let tun_retrieve: Bool + public let address: [String] + public init(name: String?, no_pi: Bool, tun_excl: Bool, tun_retrieve: Bool, address: [String]) { + self.name = name + self.no_pi = no_pi + self.tun_excl = tun_excl + self.tun_retrieve = tun_retrieve + self.address = address + } + } + public let tun: TunOptions + public init(tun: TunOptions) { + self.tun = tun + } +} + +// swiftlint:enable identifier_name raw_value_for_camel_cased_codable_enum diff --git a/Apple/NetworkExtension/NWConnection+Async.swift b/Apple/Shared/NWConnection+Async.swift similarity index 100% rename from Apple/NetworkExtension/NWConnection+Async.swift rename to Apple/Shared/NWConnection+Async.swift diff --git a/Apple/NetworkExtension/NewlineProtocolFramer.swift b/Apple/Shared/NewlineProtocolFramer.swift similarity index 100% rename from Apple/NetworkExtension/NewlineProtocolFramer.swift rename to Apple/Shared/NewlineProtocolFramer.swift diff --git a/Cargo.lock b/Cargo.lock index a75bd28..628e996 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,18 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "ahash" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -47,6 +59,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "anstream" version = "0.6.11" @@ -334,6 +352,7 @@ dependencies = [ "rand", "rand_core", "ring", + "rusqlite", "schemars", "serde", "serde_json", @@ -743,6 +762,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "2.0.1" @@ -967,6 +998,19 @@ name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692eaaf7f7607518dd3cef090f1474b61edc5301d8012f09579920df68b725ee" +dependencies = [ + "hashbrown 0.14.3", +] [[package]] name = "hdrhistogram" @@ -1258,6 +1302,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "libsqlite3-sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libsystemd" version = "0.7.0" @@ -1877,6 +1932,20 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rusqlite" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" +dependencies = [ + "bitflags 2.4.2", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2949,6 +3018,26 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Dockerfile b/Dockerfile index 9f54478..afd51ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN set -eux && \ curl --proto '=https' --tlsv1.2 -sSf https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor --output $KEYRINGS/llvm.gpg && \ echo "deb [signed-by=$KEYRINGS/llvm.gpg] http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-$LLVM_VERSION main" > /etc/apt/sources.list.d/llvm.list && \ apt-get update && \ - apt-get install --no-install-recommends -y clang-$LLVM_VERSION llvm-$LLVM_VERSION lld-$LLVM_VERSION && \ + apt-get install --no-install-recommends -y clang-$LLVM_VERSION llvm-$LLVM_VERSION lld-$LLVM_VERSION build-essential sqlite3 libsqlite3-dev musl musl-tools musl-dev && \ ln -s clang-$LLVM_VERSION /usr/bin/clang && \ ln -s clang /usr/bin/clang++ && \ ln -s lld-$LLVM_VERSION /usr/bin/ld.lld && \ @@ -24,12 +24,30 @@ RUN set -eux && \ apt-get remove -y --auto-remove && \ rm -rf /var/lib/apt/lists/* +ARG SQLITE_VERSION=3400100 + RUN case $TARGETPLATFORM in \ - "linux/arm64") LLVM_TARGET=aarch64-unknown-linux-musl ;; \ - "linux/amd64") LLVM_TARGET=x86_64-unknown-linux-musl ;; \ + "linux/arm64") LLVM_TARGET=aarch64-unknown-linux-musl MUSL_TARGET=aarch64-linux-musl ;; \ + "linux/amd64") LLVM_TARGET=x86_64-unknown-linux-musl MUSL_TARGET=x86_64-linux-musl ;; \ *) exit 1 ;; \ esac && \ - rustup target add $LLVM_TARGET + rustup target add $LLVM_TARGET && \ + curl --proto '=https' --tlsv1.2 -sSfO https://www.sqlite.org/2022/sqlite-autoconf-$SQLITE_VERSION.tar.gz && \ + tar xf sqlite-autoconf-$SQLITE_VERSION.tar.gz && \ + rm sqlite-autoconf-$SQLITE_VERSION.tar.gz && \ + cd sqlite-autoconf-$SQLITE_VERSION && \ + ./configure --disable-shared \ + CC="clang-$LLVM_VERSION -target $LLVM_TARGET" \ + CFLAGS="-I/usr/local/include -I/usr/include/$MUSL_TARGET" \ + LDFLAGS="-L/usr/local/lib -L/usr/lib/$MUSL_TARGET -L/lib/$MUSL_TARGET" && \ + make && \ + make install && \ + cd .. && \ + rm -rf sqlite-autoconf-$SQLITE_VERSION + +ENV SQLITE3_STATIC=1 \ + SQLITE3_INCLUDE_DIR=/usr/local/include \ + SQLITE3_LIB_DIR=/usr/local/lib ENV CC_x86_64_unknown_linux_musl=clang-$LLVM_VERSION \ AR_x86_64_unknown_linux_musl=llvm-ar-$LLVM_VERSION \ diff --git a/burrow-gtk/build-aux/Dockerfile b/burrow-gtk/build-aux/Dockerfile index df07c4a..4e71c05 100644 --- a/burrow-gtk/build-aux/Dockerfile +++ b/burrow-gtk/build-aux/Dockerfile @@ -4,7 +4,7 @@ ENV DEBIAN_FRONTEND=noninteractive RUN set -eux && \ dnf update -y && \ - dnf install -y clang ninja-build cmake meson gtk4-devel glib2-devel libadwaita-devel desktop-file-utils libappstream-glib util-linux wget fuse fuse-libs file + dnf install -y clang ninja-build cmake meson gtk4-devel glib2-devel libadwaita-devel desktop-file-utils libappstream-glib util-linux wget fuse fuse-libs file sqlite sqlite-devel RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal ENV PATH="/root/.cargo/bin:${PATH}" @@ -12,6 +12,8 @@ ENV PATH="/root/.cargo/bin:${PATH}" WORKDIR /app COPY . /app +ENV SQLITE3_STATIC=1 + RUN cd /app/burrow-gtk/ && \ ./build-aux/build_appimage.sh diff --git a/burrow-gtk/build-aux/build_appimage.sh b/burrow-gtk/build-aux/build_appimage.sh index cd58c17..f054cd9 100755 --- a/burrow-gtk/build-aux/build_appimage.sh +++ b/burrow-gtk/build-aux/build_appimage.sh @@ -22,6 +22,8 @@ elif [ "$ARCHITECTURE" == "aarch64" ]; then chmod a+x /tmp/linuxdeploy fi + +CFLAGS="-I/usr/local/include -I/usr/include/$MUSL_TARGET -fPIE" meson setup $BURROW_GTK_BUILD --bindir bin --prefix /usr --buildtype $BURROW_BUILD_TYPE meson compile -C $BURROW_GTK_BUILD DESTDIR=AppDir meson install -C $BURROW_GTK_BUILD diff --git a/burrow/Cargo.toml b/burrow/Cargo.toml index 4e7688b..0c816f8 100644 --- a/burrow/Cargo.toml +++ b/burrow/Cargo.toml @@ -10,7 +10,15 @@ crate-type = ["lib", "staticlib"] [dependencies] 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"] } clap = { version = "4.4", features = ["derive"] } tracing = "0.1" @@ -25,7 +33,10 @@ chacha20poly1305 = "0.10" rand = "0.8" rand_core = "0.6" aead = "0.5" -x25519-dalek = { version = "2.0", features = ["reusable_secrets", "static_secrets"] } +x25519-dalek = { version = "2.0", features = [ + "reusable_secrets", + "static_secrets", +] } ring = "0.17" parking_lot = "0.12" hmac = "0.12" @@ -37,9 +48,12 @@ async-channel = "2.1" schemars = "0.8" futures = "0.3.28" once_cell = "1.19" -console-subscriber = { version = "0.2.0" , optional = true } +console-subscriber = { version = "0.2.0", optional = true } console = "0.15.8" +[dependencies.rusqlite] +version = "0.31.0" + [target.'cfg(target_os = "linux")'.dependencies] caps = "0.5" libsystemd = "0.7" @@ -47,6 +61,7 @@ tracing-journald = "0.3" [target.'cfg(target_vendor = "apple")'.dependencies] nix = { version = "0.27" } +rusqlite = { version = "0.31.0", features = ["bundled"] } [dev-dependencies] insta = { version = "1.32", features = ["yaml"] } @@ -62,3 +77,4 @@ pre_uninstall_script = "../package/rpm/pre_uninstall" [features] tokio-console = ["dep:console-subscriber"] +bundled = ["rusqlite/bundled"] diff --git a/burrow/burrow.db b/burrow/burrow.db new file mode 100644 index 0000000..c5b6e2c Binary files /dev/null and b/burrow/burrow.db differ diff --git a/burrow/src/daemon/apple.rs b/burrow/src/daemon/apple.rs index 9460613..c60f131 100644 --- a/burrow/src/daemon/apple.rs +++ b/burrow/src/daemon/apple.rs @@ -18,7 +18,7 @@ static BURROW_NOTIFY: OnceCell> = OnceCell::new(); static BURROW_HANDLE: OnceCell = OnceCell::new(); #[no_mangle] -pub unsafe extern "C" fn spawn_in_process(path: *const c_char) { +pub unsafe extern "C" fn spawn_in_process(path: *const c_char, db_path: *const c_char) { crate::tracing::initialize(); let notify = BURROW_NOTIFY.get_or_init(|| Arc::new(Notify::new())); @@ -28,6 +28,11 @@ pub unsafe extern "C" fn spawn_in_process(path: *const c_char) { } else { Some(PathBuf::from(CStr::from_ptr(path).to_str().unwrap())) }; + let db_path_buf = if db_path.is_null() { + None + } else { + Some(PathBuf::from(CStr::from_ptr(db_path).to_str().unwrap())) + }; let sender = notify.clone(); let (handle_tx, handle_rx) = tokio::sync::oneshot::channel(); @@ -40,7 +45,12 @@ pub unsafe extern "C" fn spawn_in_process(path: *const c_char) { .unwrap(); handle_tx.send(runtime.handle().clone()).unwrap(); runtime.block_on(async { - let result = daemon_main(path_buf.as_deref(), Some(sender.clone())).await; + let result = daemon_main( + path_buf.as_deref(), + db_path_buf.as_deref(), + Some(sender.clone()), + ) + .await; if let Err(error) = result.as_ref() { error!("Burrow thread exited: {}", error); } diff --git a/burrow/src/daemon/instance.rs b/burrow/src/daemon/instance.rs index 0d3e726..bc506bd 100644 --- a/burrow/src/daemon/instance.rs +++ b/burrow/src/daemon/instance.rs @@ -1,4 +1,7 @@ -use std::sync::Arc; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; use anyhow::Result; use tokio::{sync::RwLock, task::JoinHandle}; @@ -6,11 +9,16 @@ use tracing::{debug, info, warn}; use tun::tokio::TunInterface; use crate::{ - daemon::{ - command::DaemonCommand, - response::{DaemonResponse, DaemonResponseData, ServerConfig, ServerInfo}, + daemon::rpc::{ + DaemonCommand, + DaemonNotification, + DaemonResponse, + DaemonResponseData, + ServerConfig, + ServerInfo, }, - wireguard::Interface, + database::{get_connection, load_interface}, + wireguard::{Config, Interface}, }; enum RunState { @@ -21,8 +29,11 @@ enum RunState { pub struct DaemonInstance { rx: async_channel::Receiver, sx: async_channel::Sender, + subx: async_channel::Sender, tun_interface: Arc>>, wg_interface: Arc>, + config: Arc>, + db_path: Option, wg_state: RunState, } @@ -30,13 +41,19 @@ impl DaemonInstance { pub fn new( rx: async_channel::Receiver, sx: async_channel::Sender, + subx: async_channel::Sender, wg_interface: Arc>, + config: Arc>, + db_path: Option<&Path>, ) -> Self { Self { rx, sx, + subx, wg_interface, tun_interface: Arc::new(RwLock::new(None)), + config, + db_path: db_path.map(|p| p.to_owned()), wg_state: RunState::Idle, } } @@ -59,24 +76,13 @@ impl DaemonInstance { self.tun_interface = self.wg_interface.read().await.get_tun(); debug!("tun_interface set: {:?}", self.tun_interface); - debug!("Cloning wg_interface"); let tmp_wg = self.wg_interface.clone(); - debug!("wg_interface cloned"); - - debug!("Spawning run task"); let run_task = tokio::spawn(async move { - debug!("Running wg_interface"); let twlock = tmp_wg.read().await; - debug!("wg_interface read lock acquired"); twlock.run().await }); - debug!("Run task spawned: {:?}", run_task); - - debug!("Setting wg_state to Running"); self.wg_state = RunState::Running(run_task); - debug!("wg_state set to Running"); - info!("Daemon started tun interface"); } } @@ -99,6 +105,17 @@ impl DaemonInstance { DaemonCommand::ServerConfig => { Ok(DaemonResponseData::ServerConfig(ServerConfig::default())) } + DaemonCommand::ReloadConfig(interface_id) => { + let conn = get_connection(self.db_path.as_deref())?; + let cfig = load_interface(&conn, &interface_id)?; + *self.config.write().await = cfig; + self.subx + .send(DaemonNotification::ConfigChange(ServerConfig::try_from( + &self.config.read().await.to_owned(), + )?)) + .await?; + Ok(DaemonResponseData::None) + } } } diff --git a/burrow/src/daemon/mod.rs b/burrow/src/daemon/mod.rs index 2a971dd..4469e90 100644 --- a/burrow/src/daemon/mod.rs +++ b/burrow/src/daemon/mod.rs @@ -1,40 +1,53 @@ use std::{path::Path, sync::Arc}; pub mod apple; -mod command; mod instance; mod net; -mod response; +pub mod rpc; use anyhow::Result; -pub use command::{DaemonCommand, DaemonStartOptions}; use instance::DaemonInstance; pub use net::{DaemonClient, Listener}; -pub use response::{DaemonResponse, DaemonResponseData, ServerInfo}; +pub use rpc::{DaemonCommand, DaemonResponseData, DaemonStartOptions}; use tokio::sync::{Notify, RwLock}; use tracing::{error, info}; -use crate::wireguard::{Config, Interface}; +use crate::{ + database::{get_connection, load_interface}, + wireguard::Interface, +}; -pub async fn daemon_main(path: Option<&Path>, notify_ready: Option>) -> Result<()> { +pub async fn daemon_main( + socket_path: Option<&Path>, + db_path: Option<&Path>, + notify_ready: Option>, +) -> Result<()> { let (commands_tx, commands_rx) = async_channel::unbounded(); let (response_tx, response_rx) = async_channel::unbounded(); + let (subscribe_tx, subscribe_rx) = async_channel::unbounded(); - let listener = if let Some(path) = path { + let listener = if let Some(path) = socket_path { info!("Creating listener... {:?}", path); - Listener::new_with_path(commands_tx, response_rx, path) + Listener::new_with_path(commands_tx, response_rx, subscribe_rx, path) } else { info!("Creating listener..."); - Listener::new(commands_tx, response_rx) + Listener::new(commands_tx, response_rx, subscribe_rx) }; if let Some(n) = notify_ready { n.notify_one() } let listener = listener?; - - let config = Config::default(); - let iface: Interface = config.try_into()?; - let mut instance = DaemonInstance::new(commands_rx, response_tx, Arc::new(RwLock::new(iface))); + let conn = get_connection(db_path)?; + let config = load_interface(&conn, "1")?; + let iface: Interface = config.clone().try_into()?; + let mut instance = DaemonInstance::new( + commands_rx, + response_tx, + subscribe_tx, + Arc::new(RwLock::new(iface)), + Arc::new(RwLock::new(config)), + db_path, + ); info!("Starting daemon..."); diff --git a/burrow/src/daemon/net/mod.rs b/burrow/src/daemon/net/mod.rs index fe35bae..242f479 100644 --- a/burrow/src/daemon/net/mod.rs +++ b/burrow/src/daemon/net/mod.rs @@ -1,6 +1,7 @@ -use serde::{Deserialize, Serialize}; -use super::DaemonCommand; + + + #[cfg(target_family = "unix")] mod unix; @@ -14,8 +15,4 @@ mod windows; #[cfg(target_os = "windows")] pub use windows::{DaemonClient, Listener}; -#[derive(Clone, Serialize, Deserialize)] -pub struct DaemonRequest { - pub id: u64, - pub command: DaemonCommand, -} + diff --git a/burrow/src/daemon/net/unix.rs b/burrow/src/daemon/net/unix.rs index 26e901d..70c4207 100644 --- a/burrow/src/daemon/net/unix.rs +++ b/burrow/src/daemon/net/unix.rs @@ -10,8 +10,14 @@ use tokio::{ }; use tracing::{debug, error, info}; -use super::*; -use crate::daemon::{DaemonCommand, DaemonResponse, DaemonResponseData}; +use crate::daemon::rpc::{ + DaemonCommand, + DaemonMessage, + DaemonNotification, + DaemonRequest, + DaemonResponse, + DaemonResponseData, +}; #[cfg(not(target_vendor = "apple"))] const UNIX_SOCKET_PATH: &str = "/run/burrow.sock"; @@ -19,10 +25,17 @@ const UNIX_SOCKET_PATH: &str = "/run/burrow.sock"; #[cfg(target_vendor = "apple")] const UNIX_SOCKET_PATH: &str = "burrow.sock"; -#[derive(Debug)] +fn get_socket_path() -> String { + if std::env::var("BURROW_SOCKET_PATH").is_ok() { + return std::env::var("BURROW_SOCKET_PATH").unwrap(); + } + UNIX_SOCKET_PATH.to_string() +} + pub struct Listener { cmd_tx: async_channel::Sender, rsp_rx: async_channel::Receiver, + sub_chan: async_channel::Receiver, inner: UnixListener, } @@ -31,9 +44,11 @@ impl Listener { pub fn new( cmd_tx: async_channel::Sender, rsp_rx: async_channel::Receiver, + sub_chan: async_channel::Receiver, ) -> Self { - let path = Path::new(OsStr::new(UNIX_SOCKET_PATH)); - Self::new_with_path(cmd_tx, rsp_rx, path)? + let socket_path = get_socket_path(); + let path = Path::new(OsStr::new(&socket_path)); + Self::new_with_path(cmd_tx, rsp_rx, sub_chan, path)? } #[throws] @@ -41,10 +56,16 @@ impl Listener { pub fn new_with_path( cmd_tx: async_channel::Sender, rsp_rx: async_channel::Receiver, + sub_chan: async_channel::Receiver, path: &Path, ) -> Self { let inner = listener_from_path_or_fd(&path, raw_fd())?; - Self { cmd_tx, rsp_rx, inner } + Self { + cmd_tx, + rsp_rx, + sub_chan, + inner, + } } #[throws] @@ -52,10 +73,16 @@ impl Listener { pub fn new_with_path( cmd_tx: async_channel::Sender, rsp_rx: async_channel::Receiver, + sub_chan: async_channel::Receiver, path: &Path, ) -> Self { let inner = listener_from_path(path)?; - Self { cmd_tx, rsp_rx, inner } + Self { + cmd_tx, + rsp_rx, + inner, + sub_chan, + } } pub async fn run(&self) -> Result<()> { @@ -64,9 +91,10 @@ impl Listener { let (stream, _) = self.inner.accept().await?; let cmd_tx = self.cmd_tx.clone(); let rsp_rxc = self.rsp_rx.clone(); + let sub_chan = self.sub_chan.clone(); tokio::task::spawn(async move { info!("Got connection: {:?}", stream); - Self::stream(stream, cmd_tx, rsp_rxc).await; + Self::stream(stream, cmd_tx, rsp_rxc, sub_chan).await; }); } } @@ -75,34 +103,46 @@ impl Listener { stream: UnixStream, cmd_tx: async_channel::Sender, rsp_rxc: async_channel::Receiver, + sub_chan: async_channel::Receiver, ) { let mut stream = stream; let (mut read_stream, mut write_stream) = stream.split(); let buf_reader = BufReader::new(&mut read_stream); let mut lines = buf_reader.lines(); - while let Ok(Some(line)) = lines.next_line().await { - info!("Line: {}", line); - let mut res: DaemonResponse = DaemonResponseData::None.into(); - let req = match serde_json::from_str::(&line) { - Ok(req) => Some(req), - Err(e) => { - res.result = Err(e.to_string()); - error!("Failed to parse request: {}", e); - None - } - }; - let mut res = serde_json::to_string(&res).unwrap(); - res.push('\n'); + loop { + tokio::select! { + Ok(Some(line)) = lines.next_line() => { + info!("Line: {}", line); + let mut res: DaemonResponse = DaemonResponseData::None.into(); + let req = match serde_json::from_str::(&line) { + Ok(req) => Some(req), + Err(e) => { + res.result = Err(e.to_string()); + error!("Failed to parse request: {}", e); + None + } + }; - if let Some(req) = req { - cmd_tx.send(req.command).await.unwrap(); - let res = rsp_rxc.recv().await.unwrap().with_id(req.id); - let mut retres = serde_json::to_string(&res).unwrap(); - retres.push('\n'); - info!("Sending response: {}", retres); - write_stream.write_all(retres.as_bytes()).await.unwrap(); - } else { - write_stream.write_all(res.as_bytes()).await.unwrap(); + let res = serde_json::to_string(&DaemonMessage::from(res)).unwrap(); + + if let Some(req) = req { + cmd_tx.send(req.command).await.unwrap(); + let res = rsp_rxc.recv().await.unwrap().with_id(req.id); + let mut payload = serde_json::to_string(&DaemonMessage::from(res)).unwrap(); + payload.push('\n'); + info!("Sending response: {}", payload); + write_stream.write_all(payload.as_bytes()).await.unwrap(); + } else { + write_stream.write_all(res.as_bytes()).await.unwrap(); + } + } + Ok(cmd) = sub_chan.recv() => { + info!("Got subscription command: {:?}", cmd); + let msg = DaemonMessage::from(cmd); + let mut payload = serde_json::to_string(&msg).unwrap(); + payload.push('\n'); + write_stream.write_all(payload.as_bytes()).await.unwrap(); + } } } } @@ -176,7 +216,8 @@ pub struct DaemonClient { impl DaemonClient { pub async fn new() -> Result { - let path = Path::new(OsStr::new(UNIX_SOCKET_PATH)); + let socket_path = get_socket_path(); + let path = Path::new(OsStr::new(&socket_path)); Self::new_with_path(path).await } diff --git a/burrow/src/daemon/rpc/mod.rs b/burrow/src/daemon/rpc/mod.rs new file mode 100644 index 0000000..4146e71 --- /dev/null +++ b/burrow/src/daemon/rpc/mod.rs @@ -0,0 +1,40 @@ +pub mod notification; +pub mod request; +pub mod response; + +pub use notification::DaemonNotification; +pub use request::{DaemonCommand, DaemonRequest, DaemonStartOptions}; +pub use response::{DaemonResponse, DaemonResponseData, ServerConfig, ServerInfo}; +use serde::{Deserialize, Serialize}; + +/// The `Message` object contains either a `DaemonRequest` or a `DaemonResponse` to be serialized / deserialized +/// for our IPC communication. Our IPC protocol is based on jsonrpc (https://www.jsonrpc.org/specification#overview), +/// but deviates from it in a few ways: +/// - We differentiate Notifications from Requests explicitly. +/// - We have a "type" field to differentiate between a request, a response, and a notification. +/// - The params field may receive any json value(such as a string), not just an object or an array. +#[derive(Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum DaemonMessage { + Request(DaemonRequest), + Response(DaemonResponse), + Notification(DaemonNotification), +} + +impl From for DaemonMessage { + fn from(request: DaemonRequest) -> Self { + DaemonMessage::Request(request) + } +} + +impl From for DaemonMessage { + fn from(response: DaemonResponse) -> Self { + DaemonMessage::Response(response) + } +} + +impl From for DaemonMessage { + fn from(notification: DaemonNotification) -> Self { + DaemonMessage::Notification(notification) + } +} diff --git a/burrow/src/daemon/rpc/notification.rs b/burrow/src/daemon/rpc/notification.rs new file mode 100644 index 0000000..135b0e4 --- /dev/null +++ b/burrow/src/daemon/rpc/notification.rs @@ -0,0 +1,11 @@ +use rpc::ServerConfig; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::daemon::rpc; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "method", content = "params")] +pub enum DaemonNotification { + ConfigChange(ServerConfig), +} diff --git a/burrow/src/daemon/command.rs b/burrow/src/daemon/rpc/request.rs similarity index 82% rename from burrow/src/daemon/command.rs rename to burrow/src/daemon/rpc/request.rs index 53b4108..e9480aa 100644 --- a/burrow/src/daemon/command.rs +++ b/burrow/src/daemon/rpc/request.rs @@ -3,11 +3,13 @@ use serde::{Deserialize, Serialize}; use tun::TunOptions; #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag="method", content="params")] pub enum DaemonCommand { Start(DaemonStartOptions), ServerInfo, ServerConfig, Stop, + ReloadConfig(String), } #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] @@ -15,6 +17,13 @@ pub struct DaemonStartOptions { pub tun: TunOptions, } +#[derive(Clone, Serialize, Deserialize)] +pub struct DaemonRequest { + pub id: u64, + #[serde(flatten)] + pub command: DaemonCommand, +} + #[test] fn test_daemoncommand_serialization() { insta::assert_snapshot!(serde_json::to_string(&DaemonCommand::Start( diff --git a/burrow/src/daemon/response.rs b/burrow/src/daemon/rpc/response.rs similarity index 89% rename from burrow/src/daemon/response.rs rename to burrow/src/daemon/rpc/response.rs index 37ee5d9..61c9c50 100644 --- a/burrow/src/daemon/response.rs +++ b/burrow/src/daemon/rpc/response.rs @@ -2,6 +2,8 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use tun::TunInterface; +use crate::wireguard::Config; + #[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)] pub struct DaemonResponse { // Error types can't be serialized, so this is the second best option. @@ -62,6 +64,18 @@ pub struct ServerConfig { pub mtu: Option, } +impl TryFrom<&Config> for ServerConfig { + type Error = anyhow::Error; + + fn try_from(config: &Config) -> anyhow::Result { + Ok(ServerConfig { + address: config.interface.address.clone(), + name: None, + mtu: config.interface.mtu.map(|mtu| mtu as i32), + }) + } +} + impl Default for ServerConfig { fn default() -> Self { Self { @@ -73,6 +87,7 @@ impl Default for ServerConfig { } #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "type")] pub enum DaemonResponseData { ServerInfo(ServerInfo), ServerConfig(ServerConfig), diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-2.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-2.snap new file mode 100644 index 0000000..01ec8a7 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-2.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions {\n tun: TunOptions { ..TunOptions::default() },\n })).unwrap()" +--- +{"method":"Start","params":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":[]}}} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-3.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-3.snap new file mode 100644 index 0000000..a6a0466 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-3.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::ServerInfo).unwrap()" +--- +{"method":"ServerInfo"} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-4.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-4.snap new file mode 100644 index 0000000..f930051 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-4.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::Stop).unwrap()" +--- +{"method":"Stop"} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-5.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-5.snap new file mode 100644 index 0000000..89dc42c --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-5.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::ServerConfig).unwrap()" +--- +{"method":"ServerConfig"} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization.snap new file mode 100644 index 0000000..aeca659 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions::default())).unwrap()" +--- +{"method":"Start","params":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":[]}}} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-2.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-2.snap new file mode 100644 index 0000000..d7bd712 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-2.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Ok::(DaemonResponseData::ServerInfo(ServerInfo {\n name: Some(\"burrow\".to_string()),\n ip: None,\n mtu: Some(1500),\n }))))?" +--- +{"result":{"Ok":{"type":"ServerInfo","name":"burrow","ip":null,"mtu":1500}},"id":0} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-3.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-3.snap new file mode 100644 index 0000000..30068f3 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-3.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Err::(\"error\".to_string())))?" +--- +{"result":{"Err":"error"},"id":0} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-4.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-4.snap new file mode 100644 index 0000000..c40db25 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-4.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Ok::(DaemonResponseData::ServerConfig(ServerConfig::default()))))?" +--- +{"result":{"Ok":{"type":"ServerConfig","address":["10.13.13.2"],"name":null,"mtu":null}},"id":0} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization.snap new file mode 100644 index 0000000..31bd84b --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Ok::(DaemonResponseData::None)))?" +--- +{"result":{"Ok":{"type":"None"}},"id":0} diff --git a/burrow/src/database.rs b/burrow/src/database.rs new file mode 100644 index 0000000..0047b01 --- /dev/null +++ b/burrow/src/database.rs @@ -0,0 +1,145 @@ +use std::path::Path; + +use anyhow::Result; +use rusqlite::{params, Connection}; + +use crate::wireguard::config::{Config, Interface, Peer}; + +#[cfg(target_vendor = "apple")] +const DB_PATH: &str = "burrow.db"; + +#[cfg(not(target_vendor = "apple"))] +const DB_PATH: &str = "/var/lib/burrow/burrow.db"; + +const CREATE_WG_INTERFACE_TABLE: &str = "CREATE TABLE IF NOT EXISTS wg_interface ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + listen_port INTEGER, + mtu INTEGER, + private_key TEXT NOT NULL, + address TEXT NOT NULL, + dns TEXT NOT NULL +)"; + +const CREATE_WG_PEER_TABLE: &str = "CREATE TABLE IF NOT EXISTS wg_peer ( + interface_id INT REFERENCES wg_interface(id) ON UPDATE CASCADE, + endpoint TEXT NOT NULL, + public_key TEXT NOT NULL, + allowed_ips TEXT NOT NULL, + preshared_key TEXT +)"; + +const CREATE_NETWORK_TABLE: &str = "CREATE TABLE IF NOT EXISTS network ( + interface_id INT REFERENCES wg_interface(id) ON UPDATE CASCADE +)"; + +pub fn initialize_tables(conn: &Connection) -> Result<()> { + conn.execute(CREATE_WG_INTERFACE_TABLE, [])?; + conn.execute(CREATE_WG_PEER_TABLE, [])?; + conn.execute(CREATE_NETWORK_TABLE, [])?; + Ok(()) +} + +fn parse_lst(s: &str) -> Vec { + if s.is_empty() { + return vec![]; + } + s.split(',').map(|s| s.to_string()).collect() +} + +fn to_lst(v: &Vec) -> String { + v.iter() + .map(|s| s.to_string()) + .collect::>() + .join(",") +} + +pub fn load_interface(conn: &Connection, interface_id: &str) -> Result { + let iface = conn.query_row( + "SELECT private_key, dns, address, listen_port, mtu FROM wg_interface WHERE id = ?", + [&interface_id], + |row| { + let dns_rw: String = row.get(1)?; + let dns = parse_lst(&dns_rw); + let address_rw: String = row.get(2)?; + let address = parse_lst(&address_rw); + Ok(Interface { + private_key: row.get(0)?, + dns, + address, + mtu: row.get(4)?, + listen_port: row.get(3)?, + }) + }, + )?; + let mut peers_stmt = conn.prepare("SELECT public_key, preshared_key, allowed_ips, endpoint FROM wg_peer WHERE interface_id = ?")?; + let peers = peers_stmt + .query_map([&interface_id], |row| { + let preshared_key: Option = row.get(1)?; + let allowed_ips_rw: String = row.get(2)?; + let allowed_ips: Vec = + allowed_ips_rw.split(',').map(|s| s.to_string()).collect(); + Ok(Peer { + public_key: row.get(0)?, + preshared_key, + allowed_ips, + endpoint: row.get(3)?, + persistent_keepalive: None, + name: None, + }) + })? + .collect::>>()?; + Ok(Config { interface: iface, peers }) +} + +pub fn dump_interface(conn: &Connection, config: &Config) -> Result<()> { + let mut stmt = conn.prepare("INSERT INTO wg_interface (private_key, dns, address, listen_port, mtu) VALUES (?, ?, ?, ?, ?)")?; + let cif = &config.interface; + stmt.execute(params![ + cif.private_key, + to_lst(&cif.dns), + to_lst(&cif.address), + cif.listen_port, + cif.mtu + ])?; + let interface_id = conn.last_insert_rowid(); + let mut stmt = conn.prepare("INSERT INTO wg_peer (interface_id, public_key, preshared_key, allowed_ips, endpoint) VALUES (?, ?, ?, ?, ?)")?; + for peer in &config.peers { + stmt.execute(params![ + &interface_id, + &peer.public_key, + &peer.preshared_key, + &peer.allowed_ips.join(","), + &peer.endpoint + ])?; + } + Ok(()) +} + +pub fn get_connection(path: Option<&Path>) -> Result { + let p = path.unwrap_or_else(|| std::path::Path::new(DB_PATH)); + if !p.exists() { + let conn = Connection::open(p)?; + initialize_tables(&conn)?; + dump_interface(&conn, &Config::default())?; + return Ok(conn); + } + Ok(Connection::open(p)?) +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::*; + + #[test] + fn test_db() { + let conn = Connection::open_in_memory().unwrap(); + initialize_tables(&conn).unwrap(); + let config = Config::default(); + dump_interface(&conn, &config).unwrap(); + let loaded = load_interface(&conn, "1").unwrap(); + assert_eq!(config, loaded); + } +} diff --git a/burrow/src/lib.rs b/burrow/src/lib.rs index c5406b2..d9ebf7e 100644 --- a/burrow/src/lib.rs +++ b/burrow/src/lib.rs @@ -3,16 +3,18 @@ pub mod wireguard; #[cfg(any(target_os = "linux", target_vendor = "apple"))] mod daemon; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +pub mod database; pub(crate) mod tracing; #[cfg(target_vendor = "apple")] pub use daemon::apple::spawn_in_process; #[cfg(any(target_os = "linux", target_vendor = "apple"))] pub use daemon::{ + rpc::DaemonResponse, + rpc::ServerInfo, DaemonClient, DaemonCommand, - DaemonResponse, DaemonResponseData, DaemonStartOptions, - ServerInfo, }; diff --git a/burrow/src/main.rs b/burrow/src/main.rs index 71d1c02..295373a 100644 --- a/burrow/src/main.rs +++ b/burrow/src/main.rs @@ -14,6 +14,9 @@ use tun::TunOptions; #[cfg(any(target_os = "linux", target_vendor = "apple"))] use crate::daemon::DaemonResponseData; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +pub mod database; + #[derive(Parser)] #[command(name = "Burrow")] #[command(author = "Hack Club ")] @@ -42,6 +45,14 @@ enum Commands { ServerInfo, /// Server config ServerConfig, + /// Reload Config + ReloadConfig(ReloadConfigArgs), +} + +#[derive(Args)] +struct ReloadConfigArgs { + #[clap(long, short)] + interface_id: String, } #[derive(Args)] @@ -69,13 +80,8 @@ async fn try_stop() -> Result<()> { } #[cfg(any(target_os = "linux", target_vendor = "apple"))] -async fn try_serverinfo() -> Result<()> { - let mut client = DaemonClient::new().await?; - let res = client.send_command(DaemonCommand::ServerInfo).await?; - match res.result { - Ok(DaemonResponseData::ServerInfo(si)) => { - println!("Got Result! {:?}", si); - } +fn handle_unexpected(res: Result) { + match res { Ok(DaemonResponseData::None) => { println!("Server not started.") } @@ -86,6 +92,17 @@ async fn try_serverinfo() -> Result<()> { println!("Error when retrieving from server: {}", e) } } +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_serverinfo() -> Result<()> { + let mut client = DaemonClient::new().await?; + let res = client.send_command(DaemonCommand::ServerInfo).await?; + if let Ok(DaemonResponseData::ServerInfo(si)) = res.result { + println!("Got Result! {:?}", si); + } else { + handle_unexpected(res.result); + } Ok(()) } @@ -93,40 +110,25 @@ async fn try_serverinfo() -> Result<()> { async fn try_serverconfig() -> Result<()> { let mut client = DaemonClient::new().await?; let res = client.send_command(DaemonCommand::ServerConfig).await?; - match res.result { - Ok(DaemonResponseData::ServerConfig(cfig)) => { - println!("Got Result! {:?}", cfig); - } - Ok(DaemonResponseData::None) => { - println!("Server not started.") - } - Ok(res) => { - println!("Unexpected Response: {:?}", res) - } - Err(e) => { - println!("Error when retrieving from server: {}", e) - } + if let Ok(DaemonResponseData::ServerConfig(cfig)) = res.result { + println!("Got Result! {:?}", cfig); + } else { + handle_unexpected(res.result); } Ok(()) } -#[cfg(not(any(target_os = "linux", target_vendor = "apple")))] -async fn try_start() -> Result<()> { - Ok(()) -} - -#[cfg(not(any(target_os = "linux", target_vendor = "apple")))] -async fn try_stop() -> Result<()> { - Ok(()) -} - -#[cfg(not(any(target_os = "linux", target_vendor = "apple")))] -async fn try_serverinfo() -> Result<()> { - Ok(()) -} - -#[cfg(not(any(target_os = "linux", target_vendor = "apple")))] -async fn try_serverconfig() -> Result<()> { +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_reloadconfig(interface_id: String) -> Result<()> { + let mut client = DaemonClient::new().await?; + let res = client + .send_command(DaemonCommand::ReloadConfig(interface_id)) + .await?; + if let Ok(DaemonResponseData::ServerConfig(cfig)) = res.result { + println!("Got Result! {:?}", cfig); + } else { + handle_unexpected(res.result); + } Ok(()) } @@ -139,9 +141,10 @@ async fn main() -> Result<()> { match &cli.command { Commands::Start(..) => try_start().await?, Commands::Stop => try_stop().await?, - Commands::Daemon(_) => daemon::daemon_main(None, None).await?, + Commands::Daemon(_) => daemon::daemon_main(None, None, None).await?, Commands::ServerInfo => try_serverinfo().await?, Commands::ServerConfig => try_serverconfig().await?, + Commands::ReloadConfig(args) => try_reloadconfig(args.interface_id.clone()).await?, } Ok(()) diff --git a/burrow/src/wireguard/config.rs b/burrow/src/wireguard/config.rs index ed7b3cd..bd86a9f 100644 --- a/burrow/src/wireguard/config.rs +++ b/burrow/src/wireguard/config.rs @@ -31,6 +31,7 @@ fn parse_public_key(string: &str) -> PublicKey { /// A raw version of Peer Config that can be used later to reflect configuration files. /// This should be later converted to a `WgPeer`. /// Refers to https://github.com/pirate/wireguard-docs?tab=readme-ov-file#overview +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Peer { pub public_key: String, pub preshared_key: Option, @@ -40,6 +41,7 @@ pub struct Peer { pub name: Option, } +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Interface { pub private_key: String, pub address: Vec, @@ -48,6 +50,7 @@ pub struct Interface { pub mtu: Option, } +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Config { pub peers: Vec, pub interface: Interface, // Support for multiple interfaces? diff --git a/burrow/src/wireguard/iface.rs b/burrow/src/wireguard/iface.rs index 6097082..84b5489 100755 --- a/burrow/src/wireguard/iface.rs +++ b/burrow/src/wireguard/iface.rs @@ -1,21 +1,26 @@ -use std::{net::IpAddr, sync::Arc}; -use std::ops::Deref; +use std::{net::IpAddr, ops::Deref, sync::Arc}; use anyhow::Error; use fehler::throws; use futures::future::join_all; use ip_network_table::IpNetworkTable; -use tokio::sync::{RwLock, Notify}; +use tokio::sync::{Notify, RwLock}; use tracing::{debug, error}; use tun::tokio::TunInterface; use super::{noise::Tunnel, Peer, PeerPcb}; -struct IndexedPcbs { +pub struct IndexedPcbs { pcbs: Vec>, allowed_ips: IpNetworkTable, } +impl Default for IndexedPcbs { + fn default() -> Self { + Self::new() + } +} + impl IndexedPcbs { pub fn new() -> Self { Self { @@ -49,12 +54,12 @@ impl FromIterator for IndexedPcbs { enum IfaceStatus { Running, - Idle + Idle, } pub struct Interface { - tun: Arc>>, - pcbs: Arc, + pub tun: Arc>>, + pub pcbs: Arc, status: Arc>, stop_notifier: Arc, } @@ -73,7 +78,12 @@ impl Interface { .collect::>()?; let pcbs = Arc::new(pcbs); - Self { pcbs, tun: Arc::new(RwLock::new(None)), status: Arc::new(RwLock::new(IfaceStatus::Idle)), stop_notifier: Arc::new(Notify::new()) } + Self { + pcbs, + tun: Arc::new(RwLock::new(None)), + status: Arc::new(RwLock::new(IfaceStatus::Idle)), + stop_notifier: Arc::new(Notify::new()), + } } pub async fn set_tun(&self, tun: TunInterface) { @@ -87,7 +97,7 @@ impl Interface { self.tun.clone() } - pub async fn remove_tun(&self){ + pub async fn remove_tun(&self) { let mut st = self.status.write().await; self.stop_notifier.notify_waiters(); *st = IfaceStatus::Idle; @@ -95,9 +105,7 @@ impl Interface { pub async fn run(&self) -> anyhow::Result<()> { let pcbs = self.pcbs.clone(); - let tun = self - .tun - .clone(); + let tun = self.tun.clone(); let status = self.status.clone(); let stop_notifier = self.stop_notifier.clone(); log::info!("Starting interface"); @@ -153,9 +161,7 @@ impl Interface { }; let mut tsks = vec![]; - let tun = self - .tun - .clone(); + let tun = self.tun.clone(); let outgoing = tokio::task::spawn(outgoing); tsks.push(outgoing); debug!("preparing to spawn read tasks"); diff --git a/burrow/src/wireguard/mod.rs b/burrow/src/wireguard/mod.rs index 15563fb..4c70a7f 100755 --- a/burrow/src/wireguard/mod.rs +++ b/burrow/src/wireguard/mod.rs @@ -1,4 +1,4 @@ -mod config; +pub mod config; mod iface; mod noise; mod pcb; diff --git a/tun/src/unix/apple/mod.rs b/tun/src/unix/apple/mod.rs index 6e859ca..74e93eb 100644 --- a/tun/src/unix/apple/mod.rs +++ b/tun/src/unix/apple/mod.rs @@ -1,11 +1,13 @@ -use std::{io::{Error, IoSlice}, mem, net::{Ipv4Addr, SocketAddrV4}, os::fd::{AsRawFd, FromRawFd, RawFd}, ptr}; -use std::net::{IpAddr, Ipv6Addr, SocketAddrV6}; -use std::ptr::addr_of; +use std::{ + io::{Error, IoSlice}, + mem, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4}, + os::fd::{AsRawFd, FromRawFd, RawFd}, +}; use byteorder::{ByteOrder, NetworkEndian}; use fehler::throws; -use libc::{c_char, iovec, writev, AF_INET, AF_INET6, sockaddr_in6}; -use nix::sys::socket::SockaddrIn6; +use libc::{c_char, iovec, writev, AF_INET, AF_INET6}; use socket2::{Domain, SockAddr, Socket, Type}; use tracing::{self, instrument}; @@ -69,11 +71,11 @@ impl TunInterface { #[throws] fn configure(&self, options: TunOptions) { - for addr in options.address{ + for addr in options.address { if let Ok(addr) = addr.parse::() { match addr { - IpAddr::V4(addr) => {self.set_ipv4_addr(addr)?} - IpAddr::V6(addr) => {self.set_ipv6_addr(addr)?} + IpAddr::V4(addr) => self.set_ipv4_addr(addr)?, + IpAddr::V6(addr) => self.set_ipv6_addr(addr)?, } } } @@ -146,7 +148,7 @@ impl TunInterface { } #[throws] - pub fn set_ipv6_addr(&self, addr: Ipv6Addr) { + pub fn set_ipv6_addr(&self, _addr: Ipv6Addr) { // let addr = SockAddr::from(SocketAddrV6::new(addr, 0, 0, 0)); // println!("addr: {:?}", addr); // let mut iff = self.in6_ifreq()?;