Wireguard Configuration in SQLite (#263)

#241
This commit is contained in:
Jett Chen 2024-04-22 06:01:47 +08:00 committed by GitHub
parent df549d48e6
commit abf1101484
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 988 additions and 325 deletions

34
.github/workflows/release-linux.yml vendored Normal file
View 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
View file

@ -8,11 +8,12 @@
"editor.acceptSuggestionOnEnter": "on", "editor.acceptSuggestionOnEnter": "on",
"rust-analyzer.restartServerOnConfigChange": true, "rust-analyzer.restartServerOnConfigChange": true,
"rust-analyzer.cargo.features": "all", "rust-analyzer.cargo.features": "all",
"rust-analyzer.rustfmt.extraArgs": [ "rust-analyzer.rustfmt.extraArgs": ["+nightly"],
"+nightly"
],
"[rust]": { "[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"
]
} }

View file

@ -7,13 +7,13 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
0B28F1562ABF463A000D44B0 /* DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B28F1552ABF463A000D44B0 /* DataTypes.swift */; }; 0BA6D73B2BA638D900BD4B55 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B46E8DF2AC918CA00BA2A3C /* Client.swift */; };
0B46E8E02AC918CA00BA2A3C /* 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 */; }; 43AA26D82A10004900F14CE6 /* MenuItemToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */; };
D000363D2BB8928E00E582EC /* NetworkCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363C2BB8928E00E582EC /* NetworkCarouselView.swift */; }; D000363D2BB8928E00E582EC /* NetworkCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363C2BB8928E00E582EC /* NetworkCarouselView.swift */; };
D000363F2BB895FB00E582EC /* OAuth2.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363E2BB895FB00E582EC /* OAuth2.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 */; }; D001173B2B30341C00D87C25 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D001173A2B30341C00D87C25 /* Logging.swift */; };
D00117442B30372900D87C25 /* libBurrowShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D00117382B30341C00D87C25 /* libBurrowShared.a */; }; D00117442B30372900D87C25 /* libBurrowShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D00117382B30341C00D87C25 /* libBurrowShared.a */; };
D00117452B30372C00D87C25 /* 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 */ = { D00117392B30341C00D87C25 /* Shared */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0B28F1552ABF463A000D44B0 /* DataTypes.swift */,
D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */,
D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */,
0B46E8DF2AC918CA00BA2A3C /* Client.swift */,
D001173A2B30341C00D87C25 /* Logging.swift */, D001173A2B30341C00D87C25 /* Logging.swift */,
D08252752B5C9FC4005DA378 /* Constants.swift */, D08252752B5C9FC4005DA378 /* Constants.swift */,
D00117422B30348D00D87C25 /* Shared.xcconfig */, D00117422B30348D00D87C25 /* Shared.xcconfig */,
@ -199,10 +203,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */, D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */,
0B46E8DF2AC918CA00BA2A3C /* Client.swift */,
0B28F1552ABF463A000D44B0 /* DataTypes.swift */,
D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */,
D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */,
D020F65929E4A697002790F6 /* Info.plist */, D020F65929E4A697002790F6 /* Info.plist */,
D020F66729E4A95D002790F6 /* NetworkExtension-iOS.entitlements */, D020F66729E4A95D002790F6 /* NetworkExtension-iOS.entitlements */,
D020F66629E4A95D002790F6 /* NetworkExtension-macOS.entitlements */, D020F66629E4A95D002790F6 /* NetworkExtension-macOS.entitlements */,
@ -456,7 +456,11 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D001173B2B30341C00D87C25 /* Logging.swift in Sources */, D001173B2B30341C00D87C25 /* Logging.swift in Sources */,
0BA6D73C2BA6393200BD4B55 /* NWConnection+Async.swift in Sources */,
D08252762B5C9FC4005DA378 /* Constants.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; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -464,10 +468,6 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( 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 */, D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;

View file

@ -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)
}
}
}

View file

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

View file

@ -5,10 +5,14 @@ import os
class PacketTunnelProvider: NEPacketTunnelProvider { class PacketTunnelProvider: NEPacketTunnelProvider {
private let logger = Logger.logger(for: PacketTunnelProvider.self) private let logger = Logger.logger(for: PacketTunnelProvider.self)
private var client: Client?
override init() { override init() {
do { 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 { } catch {
logger.error("Failed to spawn: \(error)") logger.error("Failed to spawn: \(error)")
} }
@ -17,33 +21,17 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
override func startTunnel(options: [String: NSObject]? = nil) async throws { override func startTunnel(options: [String: NSObject]? = nil) async throws {
do { do {
let client = try Client() let client = try Client()
self.client = client
register_events(client)
let command = BurrowRequest(id: 0, command: "ServerConfig") _ = try await self.loadTunSettings()
let data = try await client.request(command, type: Response<BurrowResult<ServerConfigData>>.self) let startRequest = Start(
tun: Start.TunOptions(
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: [] name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: []
) )
) )
) let response = try await client.request(startRequest, type: BurrowResult<AnyResponseData>.self)
) self.logger.log("Received start server response: \(String(describing: response))")
let response = try await client.request(startRequest, type: Response<BurrowResult<String>>.self)
self.logger.log("Received start server response: \(String(describing: response.result))")
} catch { } catch {
self.logger.error("Failed to start tunnel: \(error)") self.logger.error("Failed to start tunnel: \(error)")
throw error throw error
@ -53,20 +41,33 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
override func stopTunnel(with reason: NEProviderStopReason) async { override func stopTunnel(with reason: NEProviderStopReason) async {
do { do {
let client = try Client() let client = try Client()
let command = BurrowRequest(id: 0, command: "Stop") _ = try await client.single_request("Stop", type: BurrowResult<AnyResponseData>.self)
let data = try await client.request(command, type: Response<BurrowResult<String>>.self)
self.logger.log("Stopped client.") self.logger.log("Stopped client.")
} catch { } catch {
self.logger.error("Failed to stop tunnel: \(error)") self.logger.error("Failed to stop tunnel: \(error)")
} }
} }
func loadTunSettings() async throws -> ServerConfig {
private func generateTunSettings(from: ServerConfigData) -> NETunnelNetworkSettings? { guard let client = self.client else {
let cfig = from.ServerConfig 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") let nst = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
var v4Addresses = [String]() var v4Addresses = [String]()
var v6Addresses = [String]() var v6Addresses = [String]()
for addr in cfig.address { for addr in from.address {
if IPv4Address(addr) != nil { if IPv4Address(addr) != nil {
v6Addresses.append(addr) v6Addresses.append(addr)
} }
@ -81,4 +82,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
logger.log("Initialized ipv4 settings: \(nst.ipv4Settings)") logger.log("Initialized ipv4 settings: \(nst.ipv4Settings)")
return nst 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.")
}
}
} }

View file

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

106
Apple/Shared/Client.swift Normal file
View 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()
}
}

View file

@ -20,4 +20,14 @@ public enum Constants {
} }
return .success(groupContainerURL) 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)
}
}
} }

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

@ -38,6 +38,18 @@ dependencies = [
"cpufeatures", "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]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.2" version = "1.1.2"
@ -47,6 +59,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.11" version = "0.6.11"
@ -334,6 +352,7 @@ dependencies = [
"rand", "rand",
"rand_core", "rand_core",
"ring", "ring",
"rusqlite",
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
@ -743,6 +762,18 @@ dependencies = [
"pin-project-lite", "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]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.0.1" version = "2.0.1"
@ -967,6 +998,19 @@ name = "hashbrown"
version = "0.14.3" version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 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]] [[package]]
name = "hdrhistogram" name = "hdrhistogram"
@ -1258,6 +1302,17 @@ dependencies = [
"windows-sys 0.48.0", "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]] [[package]]
name = "libsystemd" name = "libsystemd"
version = "0.7.0" version = "0.7.0"
@ -1877,6 +1932,20 @@ dependencies = [
"windows-sys 0.48.0", "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]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
@ -2949,6 +3018,26 @@ dependencies = [
"linked-hash-map", "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]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.7.0" version = "1.7.0"

View file

@ -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 && \ 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 && \ 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 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-$LLVM_VERSION /usr/bin/clang && \
ln -s clang /usr/bin/clang++ && \ ln -s clang /usr/bin/clang++ && \
ln -s lld-$LLVM_VERSION /usr/bin/ld.lld && \ ln -s lld-$LLVM_VERSION /usr/bin/ld.lld && \
@ -24,12 +24,30 @@ RUN set -eux && \
apt-get remove -y --auto-remove && \ apt-get remove -y --auto-remove && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
ARG SQLITE_VERSION=3400100
RUN case $TARGETPLATFORM in \ RUN case $TARGETPLATFORM in \
"linux/arm64") LLVM_TARGET=aarch64-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 ;; \ "linux/amd64") LLVM_TARGET=x86_64-unknown-linux-musl MUSL_TARGET=x86_64-linux-musl ;; \
*) exit 1 ;; \ *) exit 1 ;; \
esac && \ 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 \ ENV CC_x86_64_unknown_linux_musl=clang-$LLVM_VERSION \
AR_x86_64_unknown_linux_musl=llvm-ar-$LLVM_VERSION \ AR_x86_64_unknown_linux_musl=llvm-ar-$LLVM_VERSION \

View file

@ -4,7 +4,7 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN set -eux && \ RUN set -eux && \
dnf update -y && \ 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 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}" ENV PATH="/root/.cargo/bin:${PATH}"
@ -12,6 +12,8 @@ ENV PATH="/root/.cargo/bin:${PATH}"
WORKDIR /app WORKDIR /app
COPY . /app COPY . /app
ENV SQLITE3_STATIC=1
RUN cd /app/burrow-gtk/ && \ RUN cd /app/burrow-gtk/ && \
./build-aux/build_appimage.sh ./build-aux/build_appimage.sh

View file

@ -22,6 +22,8 @@ elif [ "$ARCHITECTURE" == "aarch64" ]; then
chmod a+x /tmp/linuxdeploy chmod a+x /tmp/linuxdeploy
fi 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 setup $BURROW_GTK_BUILD --bindir bin --prefix /usr --buildtype $BURROW_BUILD_TYPE
meson compile -C $BURROW_GTK_BUILD meson compile -C $BURROW_GTK_BUILD
DESTDIR=AppDir meson install -C $BURROW_GTK_BUILD DESTDIR=AppDir meson install -C $BURROW_GTK_BUILD

View file

@ -10,7 +10,15 @@ crate-type = ["lib", "staticlib"]
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
tokio = { version = "1.21", features = ["rt", "macros", "sync", "io-util", "rt-multi-thread", "time", "tracing"] } tokio = { version = "1.21", features = [
"rt",
"macros",
"sync",
"io-util",
"rt-multi-thread",
"time",
"tracing",
] }
tun = { version = "0.1", path = "../tun", features = ["serde", "tokio"] } tun = { version = "0.1", path = "../tun", features = ["serde", "tokio"] }
clap = { version = "4.4", features = ["derive"] } clap = { version = "4.4", features = ["derive"] }
tracing = "0.1" tracing = "0.1"
@ -25,7 +33,10 @@ chacha20poly1305 = "0.10"
rand = "0.8" rand = "0.8"
rand_core = "0.6" rand_core = "0.6"
aead = "0.5" 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" ring = "0.17"
parking_lot = "0.12" parking_lot = "0.12"
hmac = "0.12" hmac = "0.12"
@ -40,6 +51,9 @@ once_cell = "1.19"
console-subscriber = { version = "0.2.0", optional = true } console-subscriber = { version = "0.2.0", optional = true }
console = "0.15.8" console = "0.15.8"
[dependencies.rusqlite]
version = "0.31.0"
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
caps = "0.5" caps = "0.5"
libsystemd = "0.7" libsystemd = "0.7"
@ -47,6 +61,7 @@ tracing-journald = "0.3"
[target.'cfg(target_vendor = "apple")'.dependencies] [target.'cfg(target_vendor = "apple")'.dependencies]
nix = { version = "0.27" } nix = { version = "0.27" }
rusqlite = { version = "0.31.0", features = ["bundled"] }
[dev-dependencies] [dev-dependencies]
insta = { version = "1.32", features = ["yaml"] } insta = { version = "1.32", features = ["yaml"] }
@ -62,3 +77,4 @@ pre_uninstall_script = "../package/rpm/pre_uninstall"
[features] [features]
tokio-console = ["dep:console-subscriber"] tokio-console = ["dep:console-subscriber"]
bundled = ["rusqlite/bundled"]

BIN
burrow/burrow.db Normal file

Binary file not shown.

View file

@ -18,7 +18,7 @@ static BURROW_NOTIFY: OnceCell<Arc<Notify>> = OnceCell::new();
static BURROW_HANDLE: OnceCell<Handle> = OnceCell::new(); static BURROW_HANDLE: OnceCell<Handle> = OnceCell::new();
#[no_mangle] #[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(); crate::tracing::initialize();
let notify = BURROW_NOTIFY.get_or_init(|| Arc::new(Notify::new())); 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 { } else {
Some(PathBuf::from(CStr::from_ptr(path).to_str().unwrap())) 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 sender = notify.clone();
let (handle_tx, handle_rx) = tokio::sync::oneshot::channel(); 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(); .unwrap();
handle_tx.send(runtime.handle().clone()).unwrap(); handle_tx.send(runtime.handle().clone()).unwrap();
runtime.block_on(async { 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() { if let Err(error) = result.as_ref() {
error!("Burrow thread exited: {}", error); error!("Burrow thread exited: {}", error);
} }

View file

@ -1,4 +1,7 @@
use std::sync::Arc; use std::{
path::{Path, PathBuf},
sync::Arc,
};
use anyhow::Result; use anyhow::Result;
use tokio::{sync::RwLock, task::JoinHandle}; use tokio::{sync::RwLock, task::JoinHandle};
@ -6,11 +9,16 @@ use tracing::{debug, info, warn};
use tun::tokio::TunInterface; use tun::tokio::TunInterface;
use crate::{ use crate::{
daemon::{ daemon::rpc::{
command::DaemonCommand, DaemonCommand,
response::{DaemonResponse, DaemonResponseData, ServerConfig, ServerInfo}, DaemonNotification,
DaemonResponse,
DaemonResponseData,
ServerConfig,
ServerInfo,
}, },
wireguard::Interface, database::{get_connection, load_interface},
wireguard::{Config, Interface},
}; };
enum RunState { enum RunState {
@ -21,8 +29,11 @@ enum RunState {
pub struct DaemonInstance { pub struct DaemonInstance {
rx: async_channel::Receiver<DaemonCommand>, rx: async_channel::Receiver<DaemonCommand>,
sx: async_channel::Sender<DaemonResponse>, sx: async_channel::Sender<DaemonResponse>,
subx: async_channel::Sender<DaemonNotification>,
tun_interface: Arc<RwLock<Option<TunInterface>>>, tun_interface: Arc<RwLock<Option<TunInterface>>>,
wg_interface: Arc<RwLock<Interface>>, wg_interface: Arc<RwLock<Interface>>,
config: Arc<RwLock<Config>>,
db_path: Option<PathBuf>,
wg_state: RunState, wg_state: RunState,
} }
@ -30,13 +41,19 @@ impl DaemonInstance {
pub fn new( pub fn new(
rx: async_channel::Receiver<DaemonCommand>, rx: async_channel::Receiver<DaemonCommand>,
sx: async_channel::Sender<DaemonResponse>, sx: async_channel::Sender<DaemonResponse>,
subx: async_channel::Sender<DaemonNotification>,
wg_interface: Arc<RwLock<Interface>>, wg_interface: Arc<RwLock<Interface>>,
config: Arc<RwLock<Config>>,
db_path: Option<&Path>,
) -> Self { ) -> Self {
Self { Self {
rx, rx,
sx, sx,
subx,
wg_interface, wg_interface,
tun_interface: Arc::new(RwLock::new(None)), tun_interface: Arc::new(RwLock::new(None)),
config,
db_path: db_path.map(|p| p.to_owned()),
wg_state: RunState::Idle, wg_state: RunState::Idle,
} }
} }
@ -59,24 +76,13 @@ impl DaemonInstance {
self.tun_interface = self.wg_interface.read().await.get_tun(); self.tun_interface = self.wg_interface.read().await.get_tun();
debug!("tun_interface set: {:?}", self.tun_interface); debug!("tun_interface set: {:?}", self.tun_interface);
debug!("Cloning wg_interface"); debug!("Cloning wg_interface");
let tmp_wg = self.wg_interface.clone(); let tmp_wg = self.wg_interface.clone();
debug!("wg_interface cloned");
debug!("Spawning run task");
let run_task = tokio::spawn(async move { let run_task = tokio::spawn(async move {
debug!("Running wg_interface");
let twlock = tmp_wg.read().await; let twlock = tmp_wg.read().await;
debug!("wg_interface read lock acquired");
twlock.run().await twlock.run().await
}); });
debug!("Run task spawned: {:?}", run_task);
debug!("Setting wg_state to Running");
self.wg_state = RunState::Running(run_task); self.wg_state = RunState::Running(run_task);
debug!("wg_state set to Running");
info!("Daemon started tun interface"); info!("Daemon started tun interface");
} }
} }
@ -99,6 +105,17 @@ impl DaemonInstance {
DaemonCommand::ServerConfig => { DaemonCommand::ServerConfig => {
Ok(DaemonResponseData::ServerConfig(ServerConfig::default())) 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)
}
} }
} }

View file

@ -1,40 +1,53 @@
use std::{path::Path, sync::Arc}; use std::{path::Path, sync::Arc};
pub mod apple; pub mod apple;
mod command;
mod instance; mod instance;
mod net; mod net;
mod response; pub mod rpc;
use anyhow::Result; use anyhow::Result;
pub use command::{DaemonCommand, DaemonStartOptions};
use instance::DaemonInstance; use instance::DaemonInstance;
pub use net::{DaemonClient, Listener}; pub use net::{DaemonClient, Listener};
pub use response::{DaemonResponse, DaemonResponseData, ServerInfo}; pub use rpc::{DaemonCommand, DaemonResponseData, DaemonStartOptions};
use tokio::sync::{Notify, RwLock}; use tokio::sync::{Notify, RwLock};
use tracing::{error, info}; 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 (commands_tx, commands_rx) = async_channel::unbounded();
let (response_tx, response_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); 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 { } else {
info!("Creating listener..."); info!("Creating listener...");
Listener::new(commands_tx, response_rx) Listener::new(commands_tx, response_rx, subscribe_rx)
}; };
if let Some(n) = notify_ready { if let Some(n) = notify_ready {
n.notify_one() n.notify_one()
} }
let listener = listener?; let listener = listener?;
let conn = get_connection(db_path)?;
let config = Config::default(); let config = load_interface(&conn, "1")?;
let iface: Interface = config.try_into()?; let iface: Interface = config.clone().try_into()?;
let mut instance = DaemonInstance::new(commands_rx, response_tx, Arc::new(RwLock::new(iface))); 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..."); info!("Starting daemon...");

View file

@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize};
use super::DaemonCommand;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
mod unix; mod unix;
@ -14,8 +15,4 @@ mod windows;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use windows::{DaemonClient, Listener}; pub use windows::{DaemonClient, Listener};
#[derive(Clone, Serialize, Deserialize)]
pub struct DaemonRequest {
pub id: u64,
pub command: DaemonCommand,
}

View file

@ -10,8 +10,14 @@ use tokio::{
}; };
use tracing::{debug, error, info}; use tracing::{debug, error, info};
use super::*; use crate::daemon::rpc::{
use crate::daemon::{DaemonCommand, DaemonResponse, DaemonResponseData}; DaemonCommand,
DaemonMessage,
DaemonNotification,
DaemonRequest,
DaemonResponse,
DaemonResponseData,
};
#[cfg(not(target_vendor = "apple"))] #[cfg(not(target_vendor = "apple"))]
const UNIX_SOCKET_PATH: &str = "/run/burrow.sock"; const UNIX_SOCKET_PATH: &str = "/run/burrow.sock";
@ -19,10 +25,17 @@ const UNIX_SOCKET_PATH: &str = "/run/burrow.sock";
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
const UNIX_SOCKET_PATH: &str = "burrow.sock"; const UNIX_SOCKET_PATH: &str = "burrow.sock";
#[derive(Debug)] 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 { pub struct Listener {
cmd_tx: async_channel::Sender<DaemonCommand>, cmd_tx: async_channel::Sender<DaemonCommand>,
rsp_rx: async_channel::Receiver<DaemonResponse>, rsp_rx: async_channel::Receiver<DaemonResponse>,
sub_chan: async_channel::Receiver<DaemonNotification>,
inner: UnixListener, inner: UnixListener,
} }
@ -31,9 +44,11 @@ impl Listener {
pub fn new( pub fn new(
cmd_tx: async_channel::Sender<DaemonCommand>, cmd_tx: async_channel::Sender<DaemonCommand>,
rsp_rx: async_channel::Receiver<DaemonResponse>, rsp_rx: async_channel::Receiver<DaemonResponse>,
sub_chan: async_channel::Receiver<DaemonNotification>,
) -> Self { ) -> Self {
let path = Path::new(OsStr::new(UNIX_SOCKET_PATH)); let socket_path = get_socket_path();
Self::new_with_path(cmd_tx, rsp_rx, path)? let path = Path::new(OsStr::new(&socket_path));
Self::new_with_path(cmd_tx, rsp_rx, sub_chan, path)?
} }
#[throws] #[throws]
@ -41,10 +56,16 @@ impl Listener {
pub fn new_with_path( pub fn new_with_path(
cmd_tx: async_channel::Sender<DaemonCommand>, cmd_tx: async_channel::Sender<DaemonCommand>,
rsp_rx: async_channel::Receiver<DaemonResponse>, rsp_rx: async_channel::Receiver<DaemonResponse>,
sub_chan: async_channel::Receiver<DaemonNotification>,
path: &Path, path: &Path,
) -> Self { ) -> Self {
let inner = listener_from_path_or_fd(&path, raw_fd())?; 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] #[throws]
@ -52,10 +73,16 @@ impl Listener {
pub fn new_with_path( pub fn new_with_path(
cmd_tx: async_channel::Sender<DaemonCommand>, cmd_tx: async_channel::Sender<DaemonCommand>,
rsp_rx: async_channel::Receiver<DaemonResponse>, rsp_rx: async_channel::Receiver<DaemonResponse>,
sub_chan: async_channel::Receiver<DaemonNotification>,
path: &Path, path: &Path,
) -> Self { ) -> Self {
let inner = listener_from_path(path)?; 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<()> { pub async fn run(&self) -> Result<()> {
@ -64,9 +91,10 @@ impl Listener {
let (stream, _) = self.inner.accept().await?; let (stream, _) = self.inner.accept().await?;
let cmd_tx = self.cmd_tx.clone(); let cmd_tx = self.cmd_tx.clone();
let rsp_rxc = self.rsp_rx.clone(); let rsp_rxc = self.rsp_rx.clone();
let sub_chan = self.sub_chan.clone();
tokio::task::spawn(async move { tokio::task::spawn(async move {
info!("Got connection: {:?}", stream); 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, stream: UnixStream,
cmd_tx: async_channel::Sender<DaemonCommand>, cmd_tx: async_channel::Sender<DaemonCommand>,
rsp_rxc: async_channel::Receiver<DaemonResponse>, rsp_rxc: async_channel::Receiver<DaemonResponse>,
sub_chan: async_channel::Receiver<DaemonNotification>,
) { ) {
let mut stream = stream; let mut stream = stream;
let (mut read_stream, mut write_stream) = stream.split(); let (mut read_stream, mut write_stream) = stream.split();
let buf_reader = BufReader::new(&mut read_stream); let buf_reader = BufReader::new(&mut read_stream);
let mut lines = buf_reader.lines(); let mut lines = buf_reader.lines();
while let Ok(Some(line)) = lines.next_line().await { loop {
tokio::select! {
Ok(Some(line)) = lines.next_line() => {
info!("Line: {}", line); info!("Line: {}", line);
let mut res: DaemonResponse = DaemonResponseData::None.into(); let mut res: DaemonResponse = DaemonResponseData::None.into();
let req = match serde_json::from_str::<DaemonRequest>(&line) { let req = match serde_json::from_str::<DaemonRequest>(&line) {
@ -91,20 +122,29 @@ impl Listener {
None 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 { if let Some(req) = req {
cmd_tx.send(req.command).await.unwrap(); cmd_tx.send(req.command).await.unwrap();
let res = rsp_rxc.recv().await.unwrap().with_id(req.id); let res = rsp_rxc.recv().await.unwrap().with_id(req.id);
let mut retres = serde_json::to_string(&res).unwrap(); let mut payload = serde_json::to_string(&DaemonMessage::from(res)).unwrap();
retres.push('\n'); payload.push('\n');
info!("Sending response: {}", retres); info!("Sending response: {}", payload);
write_stream.write_all(retres.as_bytes()).await.unwrap(); write_stream.write_all(payload.as_bytes()).await.unwrap();
} else { } else {
write_stream.write_all(res.as_bytes()).await.unwrap(); 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 { impl DaemonClient {
pub async fn new() -> Result<Self> { 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 Self::new_with_path(path).await
} }

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

View 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),
}

View file

@ -3,11 +3,13 @@ use serde::{Deserialize, Serialize};
use tun::TunOptions; use tun::TunOptions;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag="method", content="params")]
pub enum DaemonCommand { pub enum DaemonCommand {
Start(DaemonStartOptions), Start(DaemonStartOptions),
ServerInfo, ServerInfo,
ServerConfig, ServerConfig,
Stop, Stop,
ReloadConfig(String),
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
@ -15,6 +17,13 @@ pub struct DaemonStartOptions {
pub tun: TunOptions, pub tun: TunOptions,
} }
#[derive(Clone, Serialize, Deserialize)]
pub struct DaemonRequest {
pub id: u64,
#[serde(flatten)]
pub command: DaemonCommand,
}
#[test] #[test]
fn test_daemoncommand_serialization() { fn test_daemoncommand_serialization() {
insta::assert_snapshot!(serde_json::to_string(&DaemonCommand::Start( insta::assert_snapshot!(serde_json::to_string(&DaemonCommand::Start(

View file

@ -2,6 +2,8 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tun::TunInterface; use tun::TunInterface;
use crate::wireguard::Config;
#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)] #[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
pub struct DaemonResponse { pub struct DaemonResponse {
// Error types can't be serialized, so this is the second best option. // Error types can't be serialized, so this is the second best option.
@ -62,6 +64,18 @@ pub struct ServerConfig {
pub mtu: Option<i32>, 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 { impl Default for ServerConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -73,6 +87,7 @@ impl Default for ServerConfig {
} }
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type")]
pub enum DaemonResponseData { pub enum DaemonResponseData {
ServerInfo(ServerInfo), ServerInfo(ServerInfo),
ServerConfig(ServerConfig), ServerConfig(ServerConfig),

View file

@ -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":[]}}}

View file

@ -0,0 +1,5 @@
---
source: burrow/src/daemon/rpc/request.rs
expression: "serde_json::to_string(&DaemonCommand::ServerInfo).unwrap()"
---
{"method":"ServerInfo"}

View file

@ -0,0 +1,5 @@
---
source: burrow/src/daemon/rpc/request.rs
expression: "serde_json::to_string(&DaemonCommand::Stop).unwrap()"
---
{"method":"Stop"}

View file

@ -0,0 +1,5 @@
---
source: burrow/src/daemon/rpc/request.rs
expression: "serde_json::to_string(&DaemonCommand::ServerConfig).unwrap()"
---
{"method":"ServerConfig"}

View file

@ -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":[]}}}

View file

@ -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}

View file

@ -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}

View file

@ -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}

View file

@ -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
View 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);
}
}

View file

@ -3,16 +3,18 @@ pub mod wireguard;
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
mod daemon; mod daemon;
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
pub mod database;
pub(crate) mod tracing; pub(crate) mod tracing;
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
pub use daemon::apple::spawn_in_process; pub use daemon::apple::spawn_in_process;
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
pub use daemon::{ pub use daemon::{
rpc::DaemonResponse,
rpc::ServerInfo,
DaemonClient, DaemonClient,
DaemonCommand, DaemonCommand,
DaemonResponse,
DaemonResponseData, DaemonResponseData,
DaemonStartOptions, DaemonStartOptions,
ServerInfo,
}; };

View file

@ -14,6 +14,9 @@ use tun::TunOptions;
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
use crate::daemon::DaemonResponseData; use crate::daemon::DaemonResponseData;
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
pub mod database;
#[derive(Parser)] #[derive(Parser)]
#[command(name = "Burrow")] #[command(name = "Burrow")]
#[command(author = "Hack Club <team@hackclub.com>")] #[command(author = "Hack Club <team@hackclub.com>")]
@ -42,6 +45,14 @@ enum Commands {
ServerInfo, ServerInfo,
/// Server config /// Server config
ServerConfig, ServerConfig,
/// Reload Config
ReloadConfig(ReloadConfigArgs),
}
#[derive(Args)]
struct ReloadConfigArgs {
#[clap(long, short)]
interface_id: String,
} }
#[derive(Args)] #[derive(Args)]
@ -69,13 +80,8 @@ async fn try_stop() -> Result<()> {
} }
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
async fn try_serverinfo() -> Result<()> { fn handle_unexpected(res: Result<DaemonResponseData, String>) {
let mut client = DaemonClient::new().await?; match res {
let res = client.send_command(DaemonCommand::ServerInfo).await?;
match res.result {
Ok(DaemonResponseData::ServerInfo(si)) => {
println!("Got Result! {:?}", si);
}
Ok(DaemonResponseData::None) => { Ok(DaemonResponseData::None) => {
println!("Server not started.") println!("Server not started.")
} }
@ -86,6 +92,17 @@ async fn try_serverinfo() -> Result<()> {
println!("Error when retrieving from server: {}", e) 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(()) Ok(())
} }
@ -93,40 +110,25 @@ async fn try_serverinfo() -> Result<()> {
async fn try_serverconfig() -> Result<()> { async fn try_serverconfig() -> Result<()> {
let mut client = DaemonClient::new().await?; let mut client = DaemonClient::new().await?;
let res = client.send_command(DaemonCommand::ServerConfig).await?; let res = client.send_command(DaemonCommand::ServerConfig).await?;
match res.result { if let Ok(DaemonResponseData::ServerConfig(cfig)) = res.result {
Ok(DaemonResponseData::ServerConfig(cfig)) => {
println!("Got Result! {:?}", cfig); println!("Got Result! {:?}", cfig);
} } else {
Ok(DaemonResponseData::None) => { handle_unexpected(res.result);
println!("Server not started.")
}
Ok(res) => {
println!("Unexpected Response: {:?}", res)
}
Err(e) => {
println!("Error when retrieving from server: {}", e)
}
} }
Ok(()) Ok(())
} }
#[cfg(not(any(target_os = "linux", target_vendor = "apple")))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
async fn try_start() -> Result<()> { async fn try_reloadconfig(interface_id: String) -> Result<()> {
Ok(()) 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);
} }
#[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<()> {
Ok(()) Ok(())
} }
@ -139,9 +141,10 @@ async fn main() -> Result<()> {
match &cli.command { match &cli.command {
Commands::Start(..) => try_start().await?, Commands::Start(..) => try_start().await?,
Commands::Stop => try_stop().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::ServerInfo => try_serverinfo().await?,
Commands::ServerConfig => try_serverconfig().await?, Commands::ServerConfig => try_serverconfig().await?,
Commands::ReloadConfig(args) => try_reloadconfig(args.interface_id.clone()).await?,
} }
Ok(()) Ok(())

View file

@ -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. /// A raw version of Peer Config that can be used later to reflect configuration files.
/// This should be later converted to a `WgPeer`. /// This should be later converted to a `WgPeer`.
/// Refers to https://github.com/pirate/wireguard-docs?tab=readme-ov-file#overview /// Refers to https://github.com/pirate/wireguard-docs?tab=readme-ov-file#overview
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Peer { pub struct Peer {
pub public_key: String, pub public_key: String,
pub preshared_key: Option<String>, pub preshared_key: Option<String>,
@ -40,6 +41,7 @@ pub struct Peer {
pub name: Option<String>, pub name: Option<String>,
} }
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Interface { pub struct Interface {
pub private_key: String, pub private_key: String,
pub address: Vec<String>, pub address: Vec<String>,
@ -48,6 +50,7 @@ pub struct Interface {
pub mtu: Option<u32>, pub mtu: Option<u32>,
} }
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Config { pub struct Config {
pub peers: Vec<Peer>, pub peers: Vec<Peer>,
pub interface: Interface, // Support for multiple interfaces? pub interface: Interface, // Support for multiple interfaces?

View file

@ -1,21 +1,26 @@
use std::{net::IpAddr, sync::Arc}; use std::{net::IpAddr, ops::Deref, sync::Arc};
use std::ops::Deref;
use anyhow::Error; use anyhow::Error;
use fehler::throws; use fehler::throws;
use futures::future::join_all; use futures::future::join_all;
use ip_network_table::IpNetworkTable; use ip_network_table::IpNetworkTable;
use tokio::sync::{RwLock, Notify}; use tokio::sync::{Notify, RwLock};
use tracing::{debug, error}; use tracing::{debug, error};
use tun::tokio::TunInterface; use tun::tokio::TunInterface;
use super::{noise::Tunnel, Peer, PeerPcb}; use super::{noise::Tunnel, Peer, PeerPcb};
struct IndexedPcbs { pub struct IndexedPcbs {
pcbs: Vec<Arc<PeerPcb>>, pcbs: Vec<Arc<PeerPcb>>,
allowed_ips: IpNetworkTable<usize>, allowed_ips: IpNetworkTable<usize>,
} }
impl Default for IndexedPcbs {
fn default() -> Self {
Self::new()
}
}
impl IndexedPcbs { impl IndexedPcbs {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@ -49,12 +54,12 @@ impl FromIterator<PeerPcb> for IndexedPcbs {
enum IfaceStatus { enum IfaceStatus {
Running, Running,
Idle Idle,
} }
pub struct Interface { pub struct Interface {
tun: Arc<RwLock<Option<TunInterface>>>, pub tun: Arc<RwLock<Option<TunInterface>>>,
pcbs: Arc<IndexedPcbs>, pub pcbs: Arc<IndexedPcbs>,
status: Arc<RwLock<IfaceStatus>>, status: Arc<RwLock<IfaceStatus>>,
stop_notifier: Arc<Notify>, stop_notifier: Arc<Notify>,
} }
@ -73,7 +78,12 @@ impl Interface {
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
let pcbs = Arc::new(pcbs); 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) { pub async fn set_tun(&self, tun: TunInterface) {
@ -95,9 +105,7 @@ impl Interface {
pub async fn run(&self) -> anyhow::Result<()> { pub async fn run(&self) -> anyhow::Result<()> {
let pcbs = self.pcbs.clone(); let pcbs = self.pcbs.clone();
let tun = self let tun = self.tun.clone();
.tun
.clone();
let status = self.status.clone(); let status = self.status.clone();
let stop_notifier = self.stop_notifier.clone(); let stop_notifier = self.stop_notifier.clone();
log::info!("Starting interface"); log::info!("Starting interface");
@ -153,9 +161,7 @@ impl Interface {
}; };
let mut tsks = vec![]; let mut tsks = vec![];
let tun = self let tun = self.tun.clone();
.tun
.clone();
let outgoing = tokio::task::spawn(outgoing); let outgoing = tokio::task::spawn(outgoing);
tsks.push(outgoing); tsks.push(outgoing);
debug!("preparing to spawn read tasks"); debug!("preparing to spawn read tasks");

View file

@ -1,4 +1,4 @@
mod config; pub mod config;
mod iface; mod iface;
mod noise; mod noise;
mod pcb; mod pcb;

View file

@ -1,11 +1,13 @@
use std::{io::{Error, IoSlice}, mem, net::{Ipv4Addr, SocketAddrV4}, os::fd::{AsRawFd, FromRawFd, RawFd}, ptr}; use std::{
use std::net::{IpAddr, Ipv6Addr, SocketAddrV6}; io::{Error, IoSlice},
use std::ptr::addr_of; mem,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4},
os::fd::{AsRawFd, FromRawFd, RawFd},
};
use byteorder::{ByteOrder, NetworkEndian}; use byteorder::{ByteOrder, NetworkEndian};
use fehler::throws; use fehler::throws;
use libc::{c_char, iovec, writev, AF_INET, AF_INET6, sockaddr_in6}; use libc::{c_char, iovec, writev, AF_INET, AF_INET6};
use nix::sys::socket::SockaddrIn6;
use socket2::{Domain, SockAddr, Socket, Type}; use socket2::{Domain, SockAddr, Socket, Type};
use tracing::{self, instrument}; use tracing::{self, instrument};
@ -72,8 +74,8 @@ impl TunInterface {
for addr in options.address { for addr in options.address {
if let Ok(addr) = addr.parse::<IpAddr>() { if let Ok(addr) = addr.parse::<IpAddr>() {
match addr { match addr {
IpAddr::V4(addr) => {self.set_ipv4_addr(addr)?} IpAddr::V4(addr) => self.set_ipv4_addr(addr)?,
IpAddr::V6(addr) => {self.set_ipv6_addr(addr)?} IpAddr::V6(addr) => self.set_ipv6_addr(addr)?,
} }
} }
} }
@ -146,7 +148,7 @@ impl TunInterface {
} }
#[throws] #[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)); // let addr = SockAddr::from(SocketAddrV6::new(addr, 0, 0, 0));
// println!("addr: {:?}", addr); // println!("addr: {:?}", addr);
// let mut iff = self.in6_ifreq()?; // let mut iff = self.in6_ifreq()?;