Skip to content

Commit

Permalink
Generate NetworkSettings with IPC
Browse files Browse the repository at this point in the history
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
JettChenT committed Oct 14, 2023
1 parent 6368ca7 commit 8f6e0fa
Show file tree
Hide file tree
Showing 31 changed files with 906 additions and 114 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ xcuserdata

# Rust
target/

.DS_STORE
.idea/
8 changes: 8 additions & 0 deletions Apple/Burrow.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
objects = {

/* 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 */; };
D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00AA8962A4669BC005C8102 /* AppDelegate.swift */; };
D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */; };
Expand Down Expand Up @@ -46,6 +48,8 @@
/* End PBXCopyFilesBuildPhase 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>"; };
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>"; };
Expand Down Expand Up @@ -122,6 +126,8 @@
D020F66729E4A95D002790F6 /* NetworkExtension-iOS.entitlements */,
D020F66629E4A95D002790F6 /* NetworkExtension-macOS.entitlements */,
D020F66229E4A6E5002790F6 /* NetworkExtension.xcconfig */,
0B28F1552ABF463A000D44B0 /* DataTypes.swift */,
0B46E8DF2AC918CA00BA2A3C /* BurrowIpc.swift */,
D0B98FD729FDDB57004E7149 /* libburrow */,
);
path = NetworkExtension;
Expand Down Expand Up @@ -304,6 +310,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0B28F1562ABF463A000D44B0 /* DataTypes.swift in Sources */,
0B46E8E02AC918CA00BA2A3C /* BurrowIpc.swift in Sources */,
D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
142 changes: 142 additions & 0 deletions Apple/NetworkExtension/BurrowIpc.swift
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) {

Check warning on line 55 in Apple/NetworkExtension/BurrowIpc.swift

View workflow job for this annotation

GitHub Actions / Swift Lint

Tuples should have at most 2 members (large_tuple)
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)
}
}
40 changes: 40 additions & 0 deletions Apple/NetworkExtension/DataTypes.swift
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
2 changes: 2 additions & 0 deletions Apple/NetworkExtension/NetworkExtension-macOS.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
Expand Down
63 changes: 45 additions & 18 deletions Apple/NetworkExtension/PacketTunnelProvider.swift
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() {
}
}
3 changes: 2 additions & 1 deletion Apple/NetworkExtension/libburrow/libburrow.h
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
int retrieve();
void start_srv();
void initialize_oslog();
Loading

0 comments on commit 8f6e0fa

Please sign in to comment.