Merge branch 'main' into ios-ui

This commit is contained in:
Jasper Mayone 2023-11-05 18:44:31 -05:00 committed by GitHub
commit 1651872939
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 900 additions and 117 deletions

3
.gitignore vendored
View file

@ -3,3 +3,6 @@ xcuserdata
# Rust # Rust
target/ target/
.DS_STORE
.idea/

View file

@ -7,6 +7,8 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
0B28F1562ABF463A000D44B0 /* DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B28F1552ABF463A000D44B0 /* DataTypes.swift */; };
0B46E8E02AC918CA00BA2A3C /* BurrowIpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B46E8DF2AC918CA00BA2A3C /* BurrowIpc.swift */; };
43AA26D82A10004900F14CE6 /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuView.swift */; }; 43AA26D82A10004900F14CE6 /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuView.swift */; };
D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00AA8962A4669BC005C8102 /* AppDelegate.swift */; }; D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00AA8962A4669BC005C8102 /* AppDelegate.swift */; };
D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */; }; D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */; };
@ -46,6 +48,8 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
0B28F1552ABF463A000D44B0 /* DataTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTypes.swift; sourceTree = "<group>"; };
0B46E8DF2AC918CA00BA2A3C /* BurrowIpc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowIpc.swift; sourceTree = "<group>"; };
43AA26D72A10004900F14CE6 /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = "<group>"; }; 43AA26D72A10004900F14CE6 /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = "<group>"; };
D00AA8962A4669BC005C8102 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; D00AA8962A4669BC005C8102 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
D020F63D29E4A1FF002790F6 /* Identity.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Identity.xcconfig; sourceTree = "<group>"; }; D020F63D29E4A1FF002790F6 /* Identity.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Identity.xcconfig; sourceTree = "<group>"; };
@ -122,6 +126,8 @@
D020F66729E4A95D002790F6 /* NetworkExtension-iOS.entitlements */, D020F66729E4A95D002790F6 /* NetworkExtension-iOS.entitlements */,
D020F66629E4A95D002790F6 /* NetworkExtension-macOS.entitlements */, D020F66629E4A95D002790F6 /* NetworkExtension-macOS.entitlements */,
D020F66229E4A6E5002790F6 /* NetworkExtension.xcconfig */, D020F66229E4A6E5002790F6 /* NetworkExtension.xcconfig */,
0B28F1552ABF463A000D44B0 /* DataTypes.swift */,
0B46E8DF2AC918CA00BA2A3C /* BurrowIpc.swift */,
D0B98FD729FDDB57004E7149 /* libburrow */, D0B98FD729FDDB57004E7149 /* libburrow */,
); );
path = NetworkExtension; path = NetworkExtension;
@ -304,6 +310,8 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
0B28F1562ABF463A000D44B0 /* DataTypes.swift in Sources */,
0B46E8E02AC918CA00BA2A3C /* BurrowIpc.swift in Sources */,
D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */, D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;

View file

@ -0,0 +1,133 @@
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))
}
}
}
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.send(content: request, completion: comp)
}
}
}
final class BurrowIpc {
let connection: NWConnection
private var generator = SystemRandomNumberGenerator()
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 {
do {
let id: UInt = generator.next(upperBound: UInt.max)
var copy = request
copy.id = id
var data = try JSONEncoder().encode(request)
data.append(contentsOf: [10])
_ = try await self.connection.send_raw(data)
return try JSONDecoder().decode(Response<U>.self, from: data).result
} catch {
throw error
}
}
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 self.connection.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)
}
}

View file

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

View file

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.developer.networking.networkextension</key> <key>com.apple.developer.networking.networkextension</key>
<array> <array>
<string>packet-tunnel-provider</string> <string>packet-tunnel-provider</string>

View file

@ -1,39 +1,66 @@
import libburrow import libburrow
import NetworkExtension import NetworkExtension
import OSLog import os
class PacketTunnelProvider: NEPacketTunnelProvider { 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) { override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
let fild = libburrow.retrieve() logger.log("Starting tunnel")
if fild == -1 { if !osInitialized {
// Not sure if this is the right way to return an error libburrow.initialize_oslog()
logger.error("Failed to retrieve file descriptor for burrow.") osInitialized = true
let err = NSError(
domain: "com.hackclub.burrow",
code: 1_010,
userInfo: [NSLocalizedDescriptionKey: "Failed to find TunInterface"]
)
completionHandler(err)
} }
logger.info("fd: \(fild)") 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) completionHandler(nil)
} catch {
self.logger.error("An error occurred: \(error)")
completionHandler(error)
}
}
}
private func generateTunSettings(from: ServerConfigData) -> NETunnelNetworkSettings? {
let cfig = from.ServerConfig
guard let addr = cfig.address else {
return nil
}
// Using a makeshift remote tunnel address
let 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) { override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
completionHandler() completionHandler()
} }
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
if let handler = completionHandler { if let handler = completionHandler {
handler(messageData) handler(messageData)
} }
} }
override func sleep(completionHandler: @escaping () -> Void) { override func sleep(completionHandler: @escaping () -> Void) {
completionHandler() completionHandler()
} }
override func wake() { override func wake() {
} }
} }

View file

@ -1 +1,2 @@
int retrieve(); void start_srv();
void initialize_oslog();

217
Cargo.lock generated
View file

@ -39,16 +39,15 @@ dependencies = [
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.3.2" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
"anstyle-query", "anstyle-query",
"anstyle-wincon", "anstyle-wincon",
"colorchoice", "colorchoice",
"is-terminal",
"utf8parse", "utf8parse",
] ]
@ -78,9 +77,9 @@ dependencies = [
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "1.0.1" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"windows-sys 0.48.0", "windows-sys 0.48.0",
@ -92,6 +91,17 @@ version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "async-channel"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -202,12 +212,15 @@ name = "burrow"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-channel",
"caps", "caps",
"clap", "clap",
"env_logger", "env_logger",
"insta",
"libsystemd", "libsystemd",
"log", "log",
"nix", "nix",
"schemars",
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
@ -309,20 +322,19 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.3.10" version = "4.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384e169cc618c613d5e3ca6404dda77a8685a63e08660dcc64abaf7da7cb0c7a" checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
"once_cell",
] ]
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.3.10" version = "4.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef137bbe35aab78bdb468ccfba75a5f4d8321ae011d34063770780545176af2d" checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -332,9 +344,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.3.2" version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -354,6 +366,27 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "concurrent-queue"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "console"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"windows-sys 0.45.0",
]
[[package]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
version = "0.1.5" version = "0.1.5"
@ -424,12 +457,24 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "dyn-clone"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd"
[[package]] [[package]]
name = "either" name = "either"
version = "1.8.1" version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.32" version = "0.8.32"
@ -473,6 +518,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.9.0" version = "1.9.0"
@ -811,6 +862,20 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "insta"
version = "1.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e02c584f4595792d09509a94cdb92a3cef7592b1eb2d9877ee6f527062d0ea"
dependencies = [
"console",
"lazy_static",
"linked-hash-map",
"serde",
"similar",
"yaml-rust",
]
[[package]] [[package]]
name = "instant" name = "instant"
version = "0.1.12" version = "0.1.12"
@ -918,6 +983,12 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.3.8" version = "0.3.8"
@ -946,6 +1017,15 @@ version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.5.0"
@ -1068,6 +1148,16 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]] [[package]]
name = "object" name = "object"
version = "0.31.1" version = "0.31.1"
@ -1153,7 +1243,7 @@ dependencies = [
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"windows-targets", "windows-targets 0.48.1",
] ]
[[package]] [[package]]
@ -1260,9 +1350,24 @@ checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-syntax", "regex-syntax 0.7.2",
] ]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.7.2" version = "0.7.2"
@ -1360,6 +1465,30 @@ dependencies = [
"windows-sys 0.42.0", "windows-sys 0.42.0",
] ]
[[package]]
name = "schemars"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
dependencies = [
"dyn-clone",
"schemars_derive",
"serde",
"serde_json",
]
[[package]]
name = "schemars_derive"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 1.0.109",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.1.0" version = "1.1.0"
@ -1409,6 +1538,17 @@ dependencies = [
"syn 2.0.22", "syn 2.0.22",
] ]
[[package]]
name = "serde_derive_internals"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.99" version = "1.0.99"
@ -1480,6 +1620,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "similar"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.8" version = "0.4.8"
@ -1656,6 +1802,7 @@ dependencies = [
"bytes", "bytes",
"libc", "libc",
"mio", "mio",
"num_cpus",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
@ -1779,10 +1926,14 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
dependencies = [ dependencies = [
"matchers",
"nu-ansi-term", "nu-ansi-term",
"once_cell",
"regex",
"sharded-slab", "sharded-slab",
"smallvec", "smallvec",
"thread_local", "thread_local",
"tracing",
"tracing-core", "tracing-core",
"tracing-log", "tracing-log",
] ]
@ -1808,6 +1959,7 @@ dependencies = [
"log", "log",
"nix", "nix",
"reqwest", "reqwest",
"schemars",
"serde", "serde",
"socket2", "socket2",
"ssri", "ssri",
@ -2041,7 +2193,7 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.48.1",
] ]
[[package]] [[package]]
@ -2059,13 +2211,37 @@ dependencies = [
"windows_x86_64_msvc 0.42.2", "windows_x86_64_msvc 0.42.2",
] ]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.48.1",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
] ]
[[package]] [[package]]
@ -2182,6 +2358,15 @@ version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70" checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]] [[package]]
name = "zip" name = "zip"
version = "0.6.6" version = "0.6.6"

View file

@ -10,18 +10,20 @@ crate-type = ["lib", "staticlib"]
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
tokio = { version = "1.21", features = ["rt", "macros", "sync", "io-util"] } tokio = { version = "1.21", features = ["rt", "macros", "sync", "io-util", "rt-multi-thread"] }
tun = { version = "0.1", path = "../tun", features = ["serde"] } tun = { version = "0.1", path = "../tun", features = ["serde", "tokio"] }
clap = { version = "4.3.2", features = ["derive"] } clap = { version = "4.3.2", features = ["derive"] }
tracing = "0.1" tracing = "0.1"
tracing-log = "0.1" tracing-log = "0.1"
tracing-journald = "0.3" tracing-journald = "0.3"
tracing-oslog = {git = "https://github.com/Stormshield-robinc/tracing-oslog"} tracing-oslog = {git = "https://github.com/Stormshield-robinc/tracing-oslog"}
tracing-subscriber = "0.3" tracing-subscriber = { version = "0.3" , features = ["std", "env-filter"]}
env_logger = "0.10" env_logger = "0.10"
log = "0.4" log = "0.4"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
async-channel = "1.9"
schemars = "0.8"
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
caps = "0.5.5" caps = "0.5.5"
@ -30,6 +32,9 @@ libsystemd = "0.6"
[target.'cfg(target_vendor = "apple")'.dependencies] [target.'cfg(target_vendor = "apple")'.dependencies]
nix = { version = "0.26.2" } nix = { version = "0.26.2" }
[dev-dependencies]
insta = { version = "1.32.0", features = ["yaml"] }
[package.metadata.generate-rpm] [package.metadata.generate-rpm]
assets = [ assets = [
{ source = "target/release/burrow", dest = "/usr/bin/burrow", mode = "755" }, { source = "target/release/burrow", dest = "/usr/bin/burrow", mode = "755" },

15
burrow/src/apple.rs Normal file
View file

@ -0,0 +1,15 @@
use tracing::{debug, Subscriber};
use tracing::instrument::WithSubscriber;
use tracing_oslog::OsLogger;
use tracing_subscriber::FmtSubscriber;
use tracing_subscriber::layer::SubscriberExt;
pub use crate::daemon::start_srv;
#[no_mangle]
pub extern "C" fn initialize_oslog() {
let collector = tracing_subscriber::registry()
.with(OsLogger::new("com.hackclub.burrow", "backend"));
tracing::subscriber::set_global_default(collector).unwrap();
debug!("Initialized oslog tracing in libburrow rust FFI");
}

View file

@ -1,13 +1,32 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tun::TunOptions; use tun::TunOptions;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub enum DaemonCommand { pub enum DaemonCommand {
Start(DaemonStartOptions), Start(DaemonStartOptions),
ServerInfo,
ServerConfig,
Stop, Stop,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct DaemonStartOptions { pub struct DaemonStartOptions {
pub(super) tun: TunOptions, pub(super) tun: TunOptions,
} }
#[test]
fn test_daemoncommand_serialization() {
insta::assert_snapshot!(
serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions::default())).unwrap()
);
insta::assert_snapshot!(
serde_json::to_string(&DaemonCommand::ServerInfo).unwrap()
);
insta::assert_snapshot!(
serde_json::to_string(&DaemonCommand::Stop).unwrap()
);
insta::assert_snapshot!(
serde_json::to_string(&DaemonCommand::ServerConfig).unwrap()
)
}

View file

@ -1,40 +1,70 @@
use tracing::{debug, info, warn};
use DaemonResponse;
use crate::daemon::response::{DaemonResponseData, ServerConfig, ServerInfo};
use super::*; use super::*;
pub struct DaemonInstance { pub struct DaemonInstance {
rx: mpsc::Receiver<DaemonCommand>, rx: async_channel::Receiver<DaemonCommand>,
sx: async_channel::Sender<DaemonResponse>,
tun_interface: Option<TunInterface>, tun_interface: Option<TunInterface>,
} }
impl DaemonInstance { impl DaemonInstance {
pub fn new(rx: mpsc::Receiver<DaemonCommand>) -> Self { pub fn new(rx: async_channel::Receiver<DaemonCommand>, sx: async_channel::Sender<DaemonResponse>) -> Self {
Self { Self {
rx, rx,
sx,
tun_interface: None, tun_interface: None,
} }
} }
pub async fn run(&mut self) -> Result<()> { async fn proc_command(&mut self, command: DaemonCommand) -> Result<DaemonResponseData> {
while let Some(command) = self.rx.recv().await { info!("Daemon got command: {:?}", command);
match command { match command {
DaemonCommand::Start(options) => { DaemonCommand::Start(st) => {
if self.tun_interface.is_none() { if self.tun_interface.is_none() {
self.tun_interface = Some(options.tun.open()?); debug!("Daemon attempting start tun interface.");
eprintln!("Daemon starting tun interface."); self.tun_interface = Some(st.tun.open()?);
info!("Daemon started tun interface");
} else { } else {
eprintln!("Got start, but tun interface already up."); warn!("Got start, but tun interface already up.");
}
Ok(DaemonResponseData::None)
}
DaemonCommand::ServerInfo => {
match &self.tun_interface {
None => {Ok(DaemonResponseData::None)}
Some(ti) => {
info!("{:?}", ti);
Ok(
DaemonResponseData::ServerInfo(
ServerInfo::try_from(ti)?
)
)
}
} }
} }
DaemonCommand::Stop => { DaemonCommand::Stop => {
if self.tun_interface.is_some() { if self.tun_interface.is_some() {
self.tun_interface = None; self.tun_interface = None;
eprintln!("Daemon stopping tun interface."); info!("Daemon stopping tun interface.");
} else { } else {
eprintln!("Got stop, but tun interface is not up.") warn!("Got stop, but tun interface is not up.")
} }
Ok(DaemonResponseData::None)
}
DaemonCommand::ServerConfig => {
Ok(DaemonResponseData::ServerConfig(ServerConfig::default()))
} }
} }
} }
pub async fn run(&mut self) -> Result<()> {
while let Ok(command) = self.rx.recv().await {
let response = self.proc_command(command).await;
info!("Daemon response: {:?}", response);
self.sx.send(DaemonResponse::new(response)).await?;
}
Ok(()) Ok(())
} }
} }

View file

@ -4,6 +4,7 @@ use tokio::sync::mpsc;
mod command; mod command;
mod instance; mod instance;
mod net; mod net;
mod response;
use instance::DaemonInstance; use instance::DaemonInstance;
use net::listen; use net::listen;
@ -11,9 +12,15 @@ use net::listen;
pub use command::{DaemonCommand, DaemonStartOptions}; pub use command::{DaemonCommand, DaemonStartOptions};
pub use net::DaemonClient; pub use net::DaemonClient;
pub async fn daemon_main() -> Result<()> { #[cfg(target_vendor = "apple")]
let (tx, rx) = mpsc::channel(2); pub use net::start_srv;
let mut inst = DaemonInstance::new(rx);
tokio::try_join!(inst.run(), listen(tx)).map(|_| ()) pub use response::{DaemonResponseData, DaemonResponse, ServerInfo};
pub async fn daemon_main() -> Result<()> {
let (commands_tx, commands_rx) = async_channel::unbounded();
let (response_tx, response_rx) = async_channel::unbounded();
let mut inst = DaemonInstance::new(commands_rx, response_tx);
tokio::try_join!(inst.run(), listen(commands_tx, response_rx)).map(|_| ())
} }

View file

@ -0,0 +1,24 @@
use std::thread;
use tokio::runtime::Runtime;
use tracing::error;
use crate::daemon::{daemon_main, DaemonClient};
#[no_mangle]
pub extern "C" fn start_srv(){
let _handle = thread::spawn(move || {
let rt = Runtime::new().unwrap();
rt.block_on(async {
if let Err(e) = daemon_main().await {
error!("Error when starting rpc server: {}", e);
}
});
});
let rt = Runtime::new().unwrap();
rt.block_on(async {
loop {
if let Ok(_) = DaemonClient::new().await{
break
}
}
});
}

View file

@ -13,17 +13,19 @@ pub use systemd::{listen, DaemonClient};
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod windows; mod windows;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub use windows::{listen, DaemonClient}; pub use windows::{listen, DaemonClient};
#[cfg(target_vendor = "apple")]
mod apple;
#[cfg(target_vendor = "apple")]
pub use apple::start_srv;
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct DaemonRequest { pub struct DaemonRequest {
pub id: u32, pub id: u32,
pub command: DaemonCommand, pub command: DaemonCommand,
} }
#[derive(Clone, Serialize, Deserialize)]
pub struct DaemonResponse {
// Error types can't be serialized, so this is the second best option.
result: std::result::Result<(), String>,
}

View file

@ -1,16 +1,16 @@
use super::*; use super::*;
use std::os::fd::IntoRawFd; use std::os::fd::IntoRawFd;
pub async fn listen(cmd_tx: mpsc::Sender<DaemonCommand>) -> Result<()> { pub async fn listen(cmd_tx: async_channel::Sender<DaemonCommand>, rsp_rx: async_channel::Receiver<DaemonResponse>) -> Result<()> {
if !libsystemd::daemon::booted() || listen_with_systemd(cmd_tx.clone()).await.is_err() { if !libsystemd::daemon::booted() || listen_with_systemd(cmd_tx.clone(), rsp_rx.clone()).await.is_err() {
unix::listen(cmd_tx).await?; unix::listen(cmd_tx, rsp_rx).await?;
} }
Ok(()) Ok(())
} }
async fn listen_with_systemd(cmd_tx: mpsc::Sender<DaemonCommand>) -> Result<()> { async fn listen_with_systemd(cmd_tx: async_channel::Sender<DaemonCommand>, rsp_rx: async_channel::Receiver<DaemonResponse>) -> Result<()> {
let fds = libsystemd::activation::receive_descriptors(false).unwrap(); let fds = libsystemd::activation::receive_descriptors(false)?;
super::unix::listen_with_optional_fd(cmd_tx, Some(fds[0].clone().into_raw_fd())).await super::unix::listen_with_optional_fd(cmd_tx, rsp_rx,Some(fds[0].clone().into_raw_fd())).await
} }
pub type DaemonClient = unix::DaemonClient; pub type DaemonClient = unix::DaemonClient;

View file

@ -1,22 +1,51 @@
use super::*; use super::*;
use std::{ use std::{ascii, io, os::fd::{FromRawFd, RawFd}, os::unix::net::UnixListener as StdUnixListener, path::Path};
os::fd::{FromRawFd, RawFd}, use std::hash::Hash;
os::unix::net::UnixListener as StdUnixListener, use std::path::PathBuf;
path::Path, use anyhow::anyhow;
}; use log::log;
use tracing::info;
use tokio::{ use tokio::{
io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
net::{UnixListener, UnixStream}, net::{UnixListener, UnixStream},
}; };
use tracing::debug;
#[cfg(not(target_vendor = "apple"))]
const UNIX_SOCKET_PATH: &str = "/run/burrow.sock"; const UNIX_SOCKET_PATH: &str = "/run/burrow.sock";
pub async fn listen(cmd_tx: mpsc::Sender<DaemonCommand>) -> Result<()> { #[cfg(target_vendor = "apple")]
listen_with_optional_fd(cmd_tx, None).await const UNIX_SOCKET_PATH: &str = "burrow.sock";
#[cfg(target_os = "macos")]
fn fetch_socket_path() -> Option<PathBuf>{
let tries = vec![
"burrow.sock".to_string(),
format!("{}/Library/Containers/com.hackclub.burrow.network/Data/burrow.sock",
std::env::var("HOME").unwrap_or_default())
.to_string(),
];
for path in tries{
let path = PathBuf::from(path);
if path.exists(){
return Some(path);
}
}
None
}
#[cfg(not(target_os = "macos"))]
fn fetch_socket_path() -> Option<PathBuf>{
Some(Path::new(UNIX_SOCKET_PATH).to_path_buf())
}
pub async fn listen(cmd_tx: async_channel::Sender<DaemonCommand>, rsp_rx: async_channel::Receiver<DaemonResponse>) -> Result<()> {
listen_with_optional_fd(cmd_tx, rsp_rx, None).await
} }
pub(crate) async fn listen_with_optional_fd( pub(crate) async fn listen_with_optional_fd(
cmd_tx: mpsc::Sender<DaemonCommand>, cmd_tx: async_channel::Sender<DaemonCommand>,
rsp_rx: async_channel::Receiver<DaemonResponse>,
raw_fd: Option<RawFd>, raw_fd: Option<RawFd>,
) -> Result<()> { ) -> Result<()> {
let path = Path::new(UNIX_SOCKET_PATH); let path = Path::new(UNIX_SOCKET_PATH);
@ -32,7 +61,16 @@ pub(crate) async fn listen_with_optional_fd(
listener listener
} else { } else {
// Won't help all that much, if we use the async version of fs. // Won't help all that much, if we use the async version of fs.
std::fs::remove_file(path)?; if let Some(par) = path.parent(){
std::fs::create_dir_all(
par
)?;
}
match std::fs::remove_file(path){
Err(e) if e.kind()==io::ErrorKind::NotFound => {Ok(())}
stuff => stuff
}?;
info!("Relative path: {}", path.to_string_lossy());
UnixListener::bind(path)? UnixListener::bind(path)?
}; };
loop { loop {
@ -41,29 +79,35 @@ pub(crate) async fn listen_with_optional_fd(
// I'm pretty sure we won't need to manually join / shut this down, // I'm pretty sure we won't need to manually join / shut this down,
// `lines` will return Err during dropping, and this task should exit gracefully. // `lines` will return Err during dropping, and this task should exit gracefully.
tokio::task::spawn(async { let rsp_rxc = rsp_rx.clone();
tokio::task::spawn(async move {
let cmd_tx = cmd_tx; let cmd_tx = cmd_tx;
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 { while let Ok(Some(line)) = lines.next_line().await {
let mut res = DaemonResponse { result: Ok(()) }; info!("Got line: {}", line);
let command = match serde_json::from_str::<DaemonRequest>(&line) { debug!("Line raw data: {:?}", line.as_bytes());
Ok(req) => Some(req.command), let mut res : DaemonResponse = DaemonResponseData::None.into();
let req = match serde_json::from_str::<DaemonRequest>(&line) {
Ok(req) => Some(req),
Err(e) => { Err(e) => {
res.result = Err(format!("{}", e)); res.result = Err(e.to_string());
None None
} }
}; };
let mut res = serde_json::to_string(&res).unwrap(); let mut res = serde_json::to_string(&res).unwrap();
res.push('\n'); res.push('\n');
write_stream.write_all(res.as_bytes()).await.unwrap();
// I want this to come at the very end so that we always send a reponse back. if let Some(req) = req {
if let Some(command) = command { cmd_tx.send(req.command).await.unwrap();
cmd_tx.send(command).await.unwrap(); let res = rsp_rxc.recv().await.unwrap().with_id(req.id);
let mut retres = serde_json::to_string(&res).unwrap();
retres.push('\n');
info!("Sending response: {}", retres);
write_stream.write_all(retres.as_bytes()).await.unwrap();
} }
} }
}); });
@ -76,7 +120,12 @@ pub struct DaemonClient {
impl DaemonClient { impl DaemonClient {
pub async fn new() -> Result<Self> { pub async fn new() -> Result<Self> {
Self::new_with_path(UNIX_SOCKET_PATH).await let path = fetch_socket_path()
.ok_or(anyhow!("Failed to find socket path"))?;
// debug!("found path: {:?}", path);
let connection = UnixStream::connect(path).await?;
debug!("connected to socket");
Ok(Self { connection })
} }
pub async fn new_with_path(path: &str) -> Result<Self> { pub async fn new_with_path(path: &str) -> Result<Self> {
@ -86,17 +135,19 @@ impl DaemonClient {
Ok(Self { connection }) Ok(Self { connection })
} }
pub async fn send_command(&mut self, command: DaemonCommand) -> Result<()> { pub async fn send_command(&mut self, command: DaemonCommand) -> Result<DaemonResponse> {
let mut command = serde_json::to_string(&DaemonRequest { id: 0, command })?; let mut command = serde_json::to_string(&DaemonRequest { id: 0, command })?;
command.push('\n'); command.push('\n');
self.connection.write_all(command.as_bytes()).await?; self.connection.write_all(command.as_bytes()).await?;
let buf_reader = BufReader::new(&mut self.connection); let buf_reader = BufReader::new(&mut self.connection);
let mut lines = buf_reader.lines(); let mut lines = buf_reader.lines();
// This unwrap *should* never cause issues. let response = lines
let response = lines.next_line().await?.unwrap(); .next_line()
.await?
.ok_or(anyhow!("Failed to read response"))?;
debug!("Got raw response: {}", response);
let res: DaemonResponse = serde_json::from_str(&response)?; let res: DaemonResponse = serde_json::from_str(&response)?;
res.result.unwrap(); Ok(res)
Ok(())
} }
} }

View file

@ -1,6 +1,6 @@
use super::*; use super::*;
pub async fn listen(_: mpsc::Sender<DaemonCommand>) -> Result<()> { pub async fn listen(_cmd_tx: async_channel::Sender<DaemonCommand>, _rsp_rx: async_channel::Receiver<DaemonResponse>) -> Result<()> {
unimplemented!("This platform does not currently support daemon mode.") unimplemented!("This platform does not currently support daemon mode.")
} }

View file

@ -0,0 +1,109 @@
use anyhow::anyhow;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tun::TunInterface;
#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
pub struct DaemonResponse {
// Error types can't be serialized, so this is the second best option.
pub result: Result<DaemonResponseData, String>,
pub id: u32
}
impl DaemonResponse{
pub fn new(result: Result<DaemonResponseData, impl ToString>) -> Self{
Self{
result: result.map_err(|e| e.to_string()),
id: 0
}
}
}
impl Into<DaemonResponse> for DaemonResponseData{
fn into(self) -> DaemonResponse{
DaemonResponse::new(Ok::<DaemonResponseData, String>(self))
}
}
impl DaemonResponse{
pub fn with_id(self, id: u32) -> Self{
Self {
id,
..self
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct ServerInfo {
pub name: Option<String>,
pub ip: Option<String>,
pub mtu: Option<i32>
}
impl TryFrom<&TunInterface> for ServerInfo{
type Error = anyhow::Error;
#[cfg(any(target_os="linux",target_vendor="apple"))]
fn try_from(server: &TunInterface) -> anyhow::Result<Self> {
Ok(
ServerInfo{
name: server.name().ok(),
ip: server.ipv4_addr().ok().map(|ip| ip.to_string()),
mtu: server.mtu().ok()
}
)
}
#[cfg(not(any(target_os="linux",target_vendor="apple")))]
fn try_from(server: &TunInterface) -> anyhow::Result<Self> {
Err(anyhow!("Not implemented in this platform"))
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct ServerConfig {
pub address: Option<String>,
pub name: Option<String>,
pub mtu: Option<i32>
}
impl Default for ServerConfig {
fn default() -> Self {
Self{
address: Some("10.0.0.1".to_string()), // Dummy remote address
name: None,
mtu: None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub enum DaemonResponseData{
ServerInfo(ServerInfo),
ServerConfig(ServerConfig),
None
}
#[test]
fn test_response_serialization() -> anyhow::Result<()>{
insta::assert_snapshot!(
serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData, String>(DaemonResponseData::None)))?
);
insta::assert_snapshot!(
serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData, String>(DaemonResponseData::ServerInfo(ServerInfo{
name: Some("burrow".to_string()),
ip: None,
mtu: Some(1500)
}))))?
);
insta::assert_snapshot!(
serde_json::to_string(&DaemonResponse::new(Err::<DaemonResponseData, String>("error".to_string())))?
);
insta::assert_snapshot!(
serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData, String>(DaemonResponseData::ServerConfig(
ServerConfig::default()
))))?
);
Ok(())
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,5 @@
---
source: burrow/src/daemon/command.rs
expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions::default())).unwrap()"
---
{"Start":{"tun":{"name":null,"no_pi":null,"tun_excl":null}}}

View file

@ -0,0 +1,5 @@
---
source: burrow/src/daemon/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":{"ServerInfo":{"name":"burrow","ip":null,"mtu":1500}}},"id":0}

View file

@ -0,0 +1,5 @@
---
source: burrow/src/daemon/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/response.rs
expression: "serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData,\n String>(DaemonResponseData::ServerConfig(ServerConfig::default()))))?"
---
{"result":{"Ok":{"ServerConfig":{"address":"10.0.0.1","name":null,"mtu":null}}},"id":0}

View file

@ -0,0 +1,5 @@
---
source: burrow/src/daemon/response.rs
expression: "serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData,\n String>(DaemonResponseData::None)))?"
---
{"result":{"Ok":"None"},"id":0}

View file

@ -1,6 +1,8 @@
#![deny(missing_debug_implementations)] #![deny(missing_debug_implementations)]
pub mod ensureroot; pub mod ensureroot;
use anyhow::Result;
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
use std::{ use std::{
mem, mem,
@ -11,6 +13,15 @@ use tun::TunInterface;
// TODO Separate start and retrieve functions // TODO Separate start and retrieve functions
mod daemon;
pub use daemon::{DaemonCommand, DaemonResponseData, DaemonStartOptions, DaemonResponse, ServerInfo};
#[cfg(target_vendor = "apple")]
mod apple;
#[cfg(target_vendor = "apple")]
pub use apple::*;
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
#[no_mangle] #[no_mangle]
pub extern "C" fn retrieve() -> i32 { pub extern "C" fn retrieve() -> i32 {

View file

@ -4,12 +4,12 @@ use std::mem;
use std::os::fd::FromRawFd; use std::os::fd::FromRawFd;
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use tracing::instrument; use tracing::{instrument, Level};
use tracing_log::LogTracer; use tracing_log::LogTracer;
use tracing_oslog::OsLogger; use tracing_oslog::OsLogger;
use tracing_subscriber::{prelude::*, FmtSubscriber}; use tracing_subscriber::{prelude::*, FmtSubscriber, EnvFilter};
use tokio::io::Result; use anyhow::Result;
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
use burrow::retrieve; use burrow::retrieve;
use tun::TunInterface; use tun::TunInterface;
@ -17,6 +17,7 @@ use tun::TunInterface;
mod daemon; mod daemon;
use daemon::{DaemonClient, DaemonCommand, DaemonStartOptions}; use daemon::{DaemonClient, DaemonCommand, DaemonStartOptions};
use crate::daemon::DaemonResponseData;
#[derive(Parser)] #[derive(Parser)]
#[command(name = "Burrow")] #[command(name = "Burrow")]
@ -44,6 +45,10 @@ enum Commands {
Stop, Stop,
/// Start Burrow daemon /// Start Burrow daemon
Daemon(DaemonArgs), Daemon(DaemonArgs),
/// Server Info
ServerInfo,
/// Server config
ServerConfig,
} }
#[derive(Args)] #[derive(Args)]
@ -61,27 +66,38 @@ async fn try_start() -> Result<()> {
client client
.send_command(DaemonCommand::Start(DaemonStartOptions::default())) .send_command(DaemonCommand::Start(DaemonStartOptions::default()))
.await .await
.map(|_| ())
} }
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
#[instrument] #[instrument]
async fn try_retrieve() -> Result<()> { async fn try_retrieve() -> Result<()> {
LogTracer::init().context("Failed to initialize LogTracer").unwrap();
if cfg!(target_os = "linux") || cfg!(target_vendor = "apple") {
let maybe_layer = system_log().unwrap();
if let Some(layer) = maybe_layer {
let logger = layer.with_subscriber(FmtSubscriber::new());
tracing::subscriber::set_global_default(logger).context("Failed to set the global tracing subscriber").unwrap();
}
}
burrow::ensureroot::ensure_root(); burrow::ensureroot::ensure_root();
let iface2 = retrieve(); let iface2 = retrieve();
tracing::info!("{}", iface2); tracing::info!("{}", iface2);
Ok(()) Ok(())
} }
async fn initialize_tracing() -> Result<()> {
LogTracer::init().context("Failed to initialize LogTracer")?;
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
{
let maybe_layer = system_log()?;
if let Some(layer) = maybe_layer {
let logger = layer.with_subscriber(
FmtSubscriber::builder()
.with_line_number(true)
.with_env_filter(EnvFilter::from_default_env())
.finish()
);
tracing::subscriber::set_global_default(logger).context("Failed to set the global tracing subscriber")?;
}
}
Ok(())
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
async fn try_stop() -> Result<()> { async fn try_stop() -> Result<()> {
let mut client = DaemonClient::new().await?; let mut client = DaemonClient::new().await?;
@ -89,6 +105,44 @@ async fn try_stop() -> Result<()> {
Ok(()) Ok(())
} }
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
async fn try_serverinfo() -> Result<()>{
let mut client = DaemonClient::new().await?;
let res = client.send_command(DaemonCommand::ServerInfo).await?;
match res.result {
Ok(DaemonResponseData::ServerInfo(si)) => {
println!("Got Result! {:?}", si);
}
Ok(DaemonResponseData::None) => {
println!("Server not started.")
}
Ok(res) => {println!("Unexpected Response: {:?}", res)}
Err(e) => {
println!("Error when retrieving from server: {}", e)
}
}
Ok(())
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
async fn try_serverconfig() -> Result<()>{
let mut client = DaemonClient::new().await?;
let res = client.send_command(DaemonCommand::ServerConfig).await?;
match res.result {
Ok(DaemonResponseData::ServerConfig(cfig)) => {
println!("Got Result! {:?}", cfig);
}
Ok(DaemonResponseData::None) => {
println!("Server not started.")
}
Ok(res) => {println!("Unexpected Response: {:?}", res)}
Err(e) => {
println!("Error when retrieving from server: {}", e)
}
}
Ok(())
}
#[cfg(not(any(target_os = "linux", target_vendor = "apple")))] #[cfg(not(any(target_os = "linux", target_vendor = "apple")))]
async fn try_start() -> Result<()> { async fn try_start() -> Result<()> {
Ok(()) Ok(())
@ -104,24 +158,40 @@ async fn try_stop() -> Result<()> {
Ok(()) 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(())
}
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> { async fn main() -> Result<()> {
initialize_tracing().await?;
tracing::info!("Platform: {}", std::env::consts::OS); tracing::info!("Platform: {}", std::env::consts::OS);
let cli = Cli::parse(); let cli = Cli::parse();
match &cli.command { match &cli.command {
Commands::Start(..) => { Commands::Start(..) => {
try_start().await.unwrap(); try_start().await?;
tracing::info!("FINISHED"); tracing::info!("FINISHED");
} }
Commands::Retrieve(..) => { Commands::Retrieve(..) => {
try_retrieve().await.unwrap(); try_retrieve().await?;
tracing::info!("FINISHED"); tracing::info!("FINISHED");
} }
Commands::Stop => { Commands::Stop => {
try_stop().await.unwrap(); try_stop().await?;
} }
Commands::Daemon(_) => daemon::daemon_main().await?, Commands::Daemon(_) => daemon::daemon_main().await?,
Commands::ServerInfo => {
try_serverinfo().await?
}
Commands::ServerConfig => {
try_serverconfig().await?
}
} }
Ok(()) Ok(())
@ -141,5 +211,5 @@ fn system_log() -> anyhow::Result<Option<tracing_journald::Layer>> {
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
fn system_log() -> anyhow::Result<Option<OsLogger>> { fn system_log() -> anyhow::Result<Option<OsLogger>> {
Ok(Some(OsLogger::new("com.hackclub.burrow", "default"))) Ok(Some(OsLogger::new("com.hackclub.burrow", "burrow-cli")))
} }

View file

@ -13,11 +13,12 @@ byteorder = "1.4"
tracing = "0.1" tracing = "0.1"
log = "0.4" log = "0.4"
serde = { version = "1", features = ["derive"], optional = true } serde = { version = "1", features = ["derive"], optional = true }
schemars = { version = "0.8", optional = true }
futures = { version = "0.3.28", optional = true } futures = { version = "0.3.28", optional = true }
[features] [features]
serde = ["dep:serde"] serde = ["dep:serde", "dep:schemars"]
tokio = ["tokio/net", "dep:futures"] tokio = ["tokio/net", "dep:futures"]
[target.'cfg(feature = "tokio")'.dev-dependencies] [target.'cfg(feature = "tokio")'.dev-dependencies]

View file

@ -4,7 +4,7 @@ use std::io::Error;
use super::TunInterface; use super::TunInterface;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema))]
pub struct TunOptions { pub struct TunOptions {
/// (Windows + Linux) Name the tun interface. /// (Windows + Linux) Name the tun interface.
pub(crate) name: Option<String>, pub(crate) name: Option<String>,