parent
df549d48e6
commit
abf1101484
43 changed files with 988 additions and 325 deletions
34
.github/workflows/release-linux.yml
vendored
Normal file
34
.github/workflows/release-linux.yml
vendored
Normal file
|
|
@ -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
|
||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
|
|
@ -8,11 +8,12 @@
|
|||
"editor.acceptSuggestionOnEnter": "on",
|
||||
"rust-analyzer.restartServerOnConfigChange": true,
|
||||
"rust-analyzer.cargo.features": "all",
|
||||
"rust-analyzer.rustfmt.extraArgs": [
|
||||
"+nightly"
|
||||
],
|
||||
"rust-analyzer.rustfmt.extraArgs": ["+nightly"],
|
||||
"[rust]": {
|
||||
"editor.defaultFormatter": "rust-lang.rust-analyzer",
|
||||
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
||||
},
|
||||
"rust-analyzer.inlayHints.typeHints.enable": false
|
||||
"rust-analyzer.inlayHints.typeHints.enable": false,
|
||||
"rust-analyzer.linkedProjects": [
|
||||
"./burrow/Cargo.toml"
|
||||
]
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T>: 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<T>: Decodable where T: Decodable {
|
||||
var id: UInt
|
||||
var result: T
|
||||
}
|
||||
|
||||
struct BurrowResult<T>: 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
|
||||
|
|
@ -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<BurrowResult<ServerConfigData>>.self)
|
||||
|
||||
let encoded = try JSONEncoder().encode(data.result)
|
||||
self.logger.log("Received final data: \(String(decoding: encoded, as: UTF8.self))")
|
||||
guard let serverconfig = data.result.Ok else {
|
||||
throw BurrowError.resultIsError
|
||||
}
|
||||
guard let tunNs = 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(
|
||||
_ = 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<BurrowResult<String>>.self)
|
||||
self.logger.log("Received start server response: \(String(describing: response.result))")
|
||||
let response = try await client.request(startRequest, type: BurrowResult<AnyResponseData>.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<BurrowResult<String>>.self)
|
||||
_ = try await client.single_request("Stop", type: BurrowResult<AnyResponseData>.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<ServerConfig>.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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
106
Apple/Shared/Client.swift
Normal file
106
Apple/Shared/Client.swift
Normal file
|
|
@ -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<Data, Error>] = [:]
|
||||
private var eventMap: [NotificationType: [(Data) throws -> Void]] = [:]
|
||||
private var task: Task<Void, Error>?
|
||||
|
||||
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<T: Request, U: Decodable>(_ 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<U>.self, from: data)
|
||||
self.logger.debug("Got response data decoded: \(String(describing: res))")
|
||||
return res.result
|
||||
}
|
||||
public func request<T: Codable, U: Decodable>(_ 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<U: Decodable>(_ 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<T: Codable>(_ event: NotificationType, callable: @escaping (T) throws -> Void) {
|
||||
let action = { data in
|
||||
let decoded = try JSONDecoder().decode(Notification<T>.self, from: data)
|
||||
try callable(decoded.params)
|
||||
}
|
||||
if eventMap[event] != nil {
|
||||
eventMap[event]?.append(action)
|
||||
} else {
|
||||
eventMap[event] = [action]
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
connection.cancel()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
139
Apple/Shared/DataTypes.swift
Normal file
139
Apple/Shared/DataTypes.swift
Normal file
|
|
@ -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<T>: 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<T>: 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<T>: 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<T>: 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
|
||||
89
Cargo.lock
generated
89
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
26
Dockerfile
26
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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
BIN
burrow/burrow.db
Normal file
BIN
burrow/burrow.db
Normal file
Binary file not shown.
|
|
@ -18,7 +18,7 @@ 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) {
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<DaemonCommand>,
|
||||
sx: async_channel::Sender<DaemonResponse>,
|
||||
subx: async_channel::Sender<DaemonNotification>,
|
||||
tun_interface: Arc<RwLock<Option<TunInterface>>>,
|
||||
wg_interface: Arc<RwLock<Interface>>,
|
||||
config: Arc<RwLock<Config>>,
|
||||
db_path: Option<PathBuf>,
|
||||
wg_state: RunState,
|
||||
}
|
||||
|
||||
|
|
@ -30,13 +41,19 @@ impl DaemonInstance {
|
|||
pub fn new(
|
||||
rx: async_channel::Receiver<DaemonCommand>,
|
||||
sx: async_channel::Sender<DaemonResponse>,
|
||||
subx: async_channel::Sender<DaemonNotification>,
|
||||
wg_interface: Arc<RwLock<Interface>>,
|
||||
config: Arc<RwLock<Config>>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Arc<Notify>>) -> Result<()> {
|
||||
pub async fn daemon_main(
|
||||
socket_path: Option<&Path>,
|
||||
db_path: Option<&Path>,
|
||||
notify_ready: Option<Arc<Notify>>,
|
||||
) -> 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...");
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<DaemonCommand>,
|
||||
rsp_rx: async_channel::Receiver<DaemonResponse>,
|
||||
sub_chan: async_channel::Receiver<DaemonNotification>,
|
||||
inner: UnixListener,
|
||||
}
|
||||
|
||||
|
|
@ -31,9 +44,11 @@ impl Listener {
|
|||
pub fn new(
|
||||
cmd_tx: async_channel::Sender<DaemonCommand>,
|
||||
rsp_rx: async_channel::Receiver<DaemonResponse>,
|
||||
sub_chan: async_channel::Receiver<DaemonNotification>,
|
||||
) -> 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<DaemonCommand>,
|
||||
rsp_rx: async_channel::Receiver<DaemonResponse>,
|
||||
sub_chan: async_channel::Receiver<DaemonNotification>,
|
||||
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<DaemonCommand>,
|
||||
rsp_rx: async_channel::Receiver<DaemonResponse>,
|
||||
sub_chan: async_channel::Receiver<DaemonNotification>,
|
||||
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,12 +103,15 @@ impl Listener {
|
|||
stream: UnixStream,
|
||||
cmd_tx: async_channel::Sender<DaemonCommand>,
|
||||
rsp_rxc: async_channel::Receiver<DaemonResponse>,
|
||||
sub_chan: async_channel::Receiver<DaemonNotification>,
|
||||
) {
|
||||
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 {
|
||||
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::<DaemonRequest>(&line) {
|
||||
|
|
@ -91,20 +122,29 @@ impl Listener {
|
|||
None
|
||||
}
|
||||
};
|
||||
let mut res = serde_json::to_string(&res).unwrap();
|
||||
res.push('\n');
|
||||
|
||||
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 retres = serde_json::to_string(&res).unwrap();
|
||||
retres.push('\n');
|
||||
info!("Sending response: {}", retres);
|
||||
write_stream.write_all(retres.as_bytes()).await.unwrap();
|
||||
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<Self> {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
40
burrow/src/daemon/rpc/mod.rs
Normal file
40
burrow/src/daemon/rpc/mod.rs
Normal file
|
|
@ -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<DaemonRequest> for DaemonMessage {
|
||||
fn from(request: DaemonRequest) -> Self {
|
||||
DaemonMessage::Request(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DaemonResponse> for DaemonMessage {
|
||||
fn from(response: DaemonResponse) -> Self {
|
||||
DaemonMessage::Response(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DaemonNotification> for DaemonMessage {
|
||||
fn from(notification: DaemonNotification) -> Self {
|
||||
DaemonMessage::Notification(notification)
|
||||
}
|
||||
}
|
||||
11
burrow/src/daemon/rpc/notification.rs
Normal file
11
burrow/src/daemon/rpc/notification.rs
Normal file
|
|
@ -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),
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
@ -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<i32>,
|
||||
}
|
||||
|
||||
impl TryFrom<&Config> for ServerConfig {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(config: &Config) -> anyhow::Result<Self> {
|
||||
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),
|
||||
|
|
@ -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":[]}}}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/rpc/request.rs
|
||||
expression: "serde_json::to_string(&DaemonCommand::ServerInfo).unwrap()"
|
||||
---
|
||||
{"method":"ServerInfo"}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/rpc/request.rs
|
||||
expression: "serde_json::to_string(&DaemonCommand::Stop).unwrap()"
|
||||
---
|
||||
{"method":"Stop"}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/rpc/request.rs
|
||||
expression: "serde_json::to_string(&DaemonCommand::ServerConfig).unwrap()"
|
||||
---
|
||||
{"method":"ServerConfig"}
|
||||
|
|
@ -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":[]}}}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/rpc/response.rs
|
||||
expression: "serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData,\n String>(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}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/rpc/response.rs
|
||||
expression: "serde_json::to_string(&DaemonResponse::new(Err::<DaemonResponseData,\n String>(\"error\".to_string())))?"
|
||||
---
|
||||
{"result":{"Err":"error"},"id":0}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/rpc/response.rs
|
||||
expression: "serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData,\n String>(DaemonResponseData::ServerConfig(ServerConfig::default()))))?"
|
||||
---
|
||||
{"result":{"Ok":{"type":"ServerConfig","address":["10.13.13.2"],"name":null,"mtu":null}},"id":0}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/rpc/response.rs
|
||||
expression: "serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData,\n String>(DaemonResponseData::None)))?"
|
||||
---
|
||||
{"result":{"Ok":{"type":"None"}},"id":0}
|
||||
145
burrow/src/database.rs
Normal file
145
burrow/src/database.rs
Normal file
|
|
@ -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<String> {
|
||||
if s.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
s.split(',').map(|s| s.to_string()).collect()
|
||||
}
|
||||
|
||||
fn to_lst<T: ToString>(v: &Vec<T>) -> String {
|
||||
v.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
}
|
||||
|
||||
pub fn load_interface(conn: &Connection, interface_id: &str) -> Result<Config> {
|
||||
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<String> = row.get(1)?;
|
||||
let allowed_ips_rw: String = row.get(2)?;
|
||||
let allowed_ips: Vec<String> =
|
||||
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::<rusqlite::Result<Vec<Peer>>>()?;
|
||||
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<Connection> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 <team@hackclub.com>")]
|
||||
|
|
@ -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<DaemonResponseData, String>) {
|
||||
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)) => {
|
||||
if let Ok(DaemonResponseData::ServerConfig(cfig)) = res.result {
|
||||
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)
|
||||
}
|
||||
} 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(())
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
|
|
@ -40,6 +41,7 @@ pub struct Peer {
|
|||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Interface {
|
||||
pub private_key: String,
|
||||
pub address: Vec<String>,
|
||||
|
|
@ -48,6 +50,7 @@ pub struct Interface {
|
|||
pub mtu: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Config {
|
||||
pub peers: Vec<Peer>,
|
||||
pub interface: Interface, // Support for multiple interfaces?
|
||||
|
|
|
|||
|
|
@ -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<Arc<PeerPcb>>,
|
||||
allowed_ips: IpNetworkTable<usize>,
|
||||
}
|
||||
|
||||
impl Default for IndexedPcbs {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexedPcbs {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
|
@ -49,12 +54,12 @@ impl FromIterator<PeerPcb> for IndexedPcbs {
|
|||
|
||||
enum IfaceStatus {
|
||||
Running,
|
||||
Idle
|
||||
Idle,
|
||||
}
|
||||
|
||||
pub struct Interface {
|
||||
tun: Arc<RwLock<Option<TunInterface>>>,
|
||||
pcbs: Arc<IndexedPcbs>,
|
||||
pub tun: Arc<RwLock<Option<TunInterface>>>,
|
||||
pub pcbs: Arc<IndexedPcbs>,
|
||||
status: Arc<RwLock<IfaceStatus>>,
|
||||
stop_notifier: Arc<Notify>,
|
||||
}
|
||||
|
|
@ -73,7 +78,12 @@ impl Interface {
|
|||
.collect::<Result<_, _>>()?;
|
||||
|
||||
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");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
mod config;
|
||||
pub mod config;
|
||||
mod iface;
|
||||
mod noise;
|
||||
mod pcb;
|
||||
|
|
|
|||
|
|
@ -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::<IpAddr>() {
|
||||
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()?;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue