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

View file

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

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 {
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(
name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: []
)
)
_ = try await self.loadTunSettings()
let startRequest = Start(
tun: Start.TunOptions(
name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: []
)
)
let response = try await client.request(startRequest, type: Response<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.")
}
}
}

View file

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