-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This generates and applies NetworkSettings object with unix socket IPC. - domain socket, json-rpc based communication - switches to anyhow for burrow crate - adds support for starting daemons on macos
- Loading branch information
Showing
31 changed files
with
906 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,6 @@ xcuserdata | |
|
||
# Rust | ||
target/ | ||
|
||
.DS_STORE | ||
.idea/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import Foundation | ||
import Network | ||
import os | ||
|
||
final class LineProtocol: NWProtocolFramerImplementation { | ||
static let definition = NWProtocolFramer.Definition(implementation: LineProtocol.self) | ||
static let label = "Lines" | ||
init(framer: NWProtocolFramer.Instance) { } | ||
func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult { .ready } | ||
func stop(framer: NWProtocolFramer.Instance) -> Bool { true } | ||
func wakeup(framer: NWProtocolFramer.Instance) { } | ||
func cleanup(framer: NWProtocolFramer.Instance) { } | ||
func lines(from buffer: UnsafeMutableRawBufferPointer?) -> (lines: [Data], size: Int)? { | ||
guard let buffer = buffer else { return nil } | ||
let lines = buffer | ||
.split(separator: 10) | ||
guard !lines.isEmpty else { return nil } | ||
let size = lines | ||
.lazy | ||
.map(\.count) | ||
.reduce(0, +) + lines.count | ||
let strings = lines | ||
.lazy | ||
.map { Data($0) } | ||
return (lines: Array(strings), size: size) | ||
} | ||
func handleInput(framer: NWProtocolFramer.Instance) -> Int { | ||
var result: [Data] = [] | ||
framer.parseInput(minimumIncompleteLength: 1, maximumLength: 16_000) { buffer, _ in | ||
guard let (lines, size) = lines(from: buffer) else { | ||
return 0 | ||
} | ||
result = lines | ||
return size | ||
} | ||
for line in result { | ||
framer.deliverInput(data: line, message: .init(instance: framer), isComplete: true) | ||
} | ||
return 0 | ||
} | ||
func handleOutput( | ||
framer: NWProtocolFramer.Instance, | ||
message: NWProtocolFramer.Message, | ||
messageLength: Int, | ||
isComplete: Bool | ||
) { | ||
do { | ||
try framer.writeOutputNoCopy(length: messageLength) | ||
} catch { | ||
} | ||
} | ||
} | ||
|
||
extension NWConnection { | ||
func receiveMessage() async throws -> (Data?, NWConnection.ContentContext?, Bool) { | ||
try await withUnsafeThrowingContinuation { continuation in | ||
receiveMessage { completeContent, contentContext, isComplete, error in | ||
if let error = error { | ||
continuation.resume(throwing: error) | ||
} | ||
continuation.resume(returning: (completeContent, contentContext, isComplete)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
final class BurrowIpc { | ||
let connection: NWConnection | ||
private var generator = SystemRandomNumberGenerator() | ||
private var continuations: [UInt: UnsafeContinuation<Data, Error>] = [:] | ||
private var logger: Logger | ||
init(logger: Logger) { | ||
let params = NWParameters.tcp | ||
params.defaultProtocolStack | ||
.applicationProtocols | ||
.insert(NWProtocolFramer.Options(definition: LineProtocol.definition), at: 0) | ||
let connection = NWConnection(to: .unix(path: "burrow.sock"), using: params) | ||
connection.start(queue: .global()) | ||
self.connection = connection | ||
self.logger = logger | ||
} | ||
func send<T: Request, U: Decodable>(_ request: T) async throws -> U { | ||
let data: Data = try await withUnsafeThrowingContinuation { continuation in | ||
let id: UInt = generator.next(upperBound: UInt.max) | ||
continuations[id] = continuation | ||
var copy = request | ||
copy.id = id | ||
do { | ||
var data = try JSONEncoder().encode(request) | ||
data.append(contentsOf: [10]) | ||
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 | ||
} | ||
} | ||
return try JSONDecoder().decode(Response<U>.self, from: data).result | ||
} | ||
func send_raw(_ request: Data) async throws -> Data { | ||
try await withCheckedThrowingContinuation { continuation in | ||
let comp: NWConnection.SendCompletion = .contentProcessed {error in | ||
if let error = error { | ||
continuation.resume(with: .failure(error)) | ||
} else { | ||
continuation.resume(with: .success(request)) | ||
} | ||
} | ||
self.connection.send(content: request, completion: comp) | ||
} | ||
} | ||
|
||
func receive_raw() async throws -> Data { | ||
let (completeContent, _, _) = try await connection.receiveMessage() | ||
self.logger.info("Received raw message response") | ||
guard let data = completeContent else { | ||
throw BurrowError.resultIsNone | ||
} | ||
return data | ||
} | ||
|
||
func request<U: Decodable>(_ request: Request, type: U.Type) async throws -> U { | ||
do { | ||
var data: Data = try JSONEncoder().encode(request) | ||
data.append(contentsOf: [10]) | ||
try await send_raw(data) | ||
self.logger.debug("message sent") | ||
let receivedData = try await receive_raw() | ||
self.logger.info("Received result: \(String(decoding: receivedData, as: UTF8.self))") | ||
return try self.parse_response(receivedData) | ||
} catch { | ||
throw error | ||
} | ||
} | ||
|
||
func parse_response<U: Decodable>(_ response: Data) throws -> U { | ||
try JSONDecoder().decode(U.self, from: response) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import Foundation | ||
|
||
enum BurrowError: Error { | ||
case addrDoesntExist | ||
case resultIsError | ||
case cantParseResult | ||
case resultIsNone | ||
} | ||
|
||
protocol Request: Codable { | ||
var id: UInt { get set } | ||
var command: String { get set } | ||
} | ||
|
||
struct BurrowRequest: Request { | ||
var id: UInt | ||
var command: String | ||
} | ||
|
||
struct Response<T>: Decodable where T: Decodable { | ||
var id: UInt | ||
var result: T | ||
} | ||
|
||
// swiftlint:disable identifier_name | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,66 @@ | ||
import libburrow | ||
import NetworkExtension | ||
import OSLog | ||
import os | ||
|
||
class PacketTunnelProvider: NEPacketTunnelProvider { | ||
let logger = Logger(subsystem: "com.hackclub.burrow", category: "General") | ||
let logger = Logger(subsystem: "com.hackclub.burrow", category: "frontend") | ||
var client: BurrowIpc? | ||
var osInitialized = false | ||
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { | ||
let fild = libburrow.retrieve() | ||
if fild == -1 { | ||
// Not sure if this is the right way to return an error | ||
logger.error("Failed to retrieve file descriptor for burrow.") | ||
let err = NSError( | ||
domain: "com.hackclub.burrow", | ||
code: 1_010, | ||
userInfo: [NSLocalizedDescriptionKey: "Failed to find TunInterface"] | ||
) | ||
completionHandler(err) | ||
logger.log("Starting tunnel") | ||
if !osInitialized { | ||
libburrow.initialize_oslog() | ||
osInitialized = true | ||
} | ||
libburrow.start_srv() | ||
client = BurrowIpc(logger: logger) | ||
logger.info("Started server") | ||
Task { | ||
do { | ||
let command = BurrowRequest(id: 0, command: "ServerConfig") | ||
guard let data = try await client?.request(command, type: Response<BurrowResult<ServerConfigData>>.self) | ||
else { | ||
throw BurrowError.cantParseResult | ||
} | ||
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 = self.generateTunSettings(from: serverconfig) else { | ||
throw BurrowError.addrDoesntExist | ||
} | ||
try await self.setTunnelNetworkSettings(tunNs) | ||
self.logger.info("Set remote tunnel address to \(tunNs.tunnelRemoteAddress)") | ||
completionHandler(nil) | ||
} catch { | ||
self.logger.error("An error occurred: \(error)") | ||
completionHandler(error) | ||
} | ||
} | ||
logger.info("fd: \(fild)") | ||
completionHandler(nil) | ||
} | ||
|
||
private func generateTunSettings(from: ServerConfigData) -> NETunnelNetworkSettings? { | ||
let cfig = from.ServerConfig | ||
guard let addr = cfig.address else { | ||
return nil | ||
} | ||
// Using a makeshift remote tunnel address | ||
var nst = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1") | ||
nst.ipv4Settings = NEIPv4Settings(addresses: [addr], subnetMasks: ["255.255.255.0"]) | ||
logger.log("Initialized ipv4 settings: \(nst.ipv4Settings)") | ||
return nst | ||
} | ||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { | ||
completionHandler() | ||
} | ||
|
||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { | ||
if let handler = completionHandler { | ||
handler(messageData) | ||
} | ||
} | ||
|
||
override func sleep(completionHandler: @escaping () -> Void) { | ||
completionHandler() | ||
} | ||
|
||
override func wake() { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
int retrieve(); | ||
void start_srv(); | ||
void initialize_oslog(); |
Oops, something went wrong.