[AltDaemon] Replaces local socket communication with XPC

Allows AltDaemon to be launched on demand + reject any connections not made from AltStore.
This commit is contained in:
Riley Testut
2020-09-22 15:11:42 -07:00
parent 361b84e3a1
commit af7fe484a2
15 changed files with 419 additions and 241 deletions

View File

@@ -14,9 +14,9 @@ import AltStoreCore
class ServerConnection
{
var server: Server
var connection: NWConnection
var connection: Connection
init(server: Server, connection: NWConnection)
init(server: Server, connection: Connection)
{
self.server = server
self.connection = connection
@@ -37,17 +37,15 @@ class ServerConnection
data = try JSONEncoder().encode(payload)
}
func process(_ error: Error?) -> Bool
func process<T>(_ result: Result<T, ALTServerError>) -> Bool
{
if error != nil
switch result
{
completionHandler(.failure(ConnectionError.connectionDropped))
case .success: return true
case .failure(let error):
completionHandler(.failure(error))
return false
}
else
{
return true
}
}
if prependSize
@@ -55,22 +53,21 @@ class ServerConnection
let requestSize = Int32(data.count)
let requestSizeData = withUnsafeBytes(of: requestSize) { Data($0) }
self.connection.send(content: requestSizeData, completion: .contentProcessed { (error) in
guard process(error) else { return }
self.connection.send(requestSizeData) { (result) in
guard process(result) else { return }
self.connection.send(content: data, completion: .contentProcessed { (error) in
guard process(error) else { return }
self.connection.send(data) { (result) in
guard process(result) else { return }
completionHandler(.success(()))
})
})
}
}
}
else
{
connection.send(content: data, completion: .contentProcessed { (error) in
guard process(error) else { return }
self.connection.send(data) { (result) in
guard process(result) else { return }
completionHandler(.success(()))
})
}
}
}
catch
@@ -84,16 +81,16 @@ class ServerConnection
{
let size = MemoryLayout<Int32>.size
self.connection.receive(minimumIncompleteLength: size, maximumLength: size) { (data, _, _, error) in
self.connection.receiveData(expectedSize: size) { (result) in
do
{
let data = try self.process(data: data, error: error)
let data = try result.get()
let expectedBytes = Int(data.withUnsafeBytes { $0.load(as: Int32.self) })
self.connection.receive(minimumIncompleteLength: expectedBytes, maximumLength: expectedBytes) { (data, _, _, error) in
self.connection.receiveData(expectedSize: expectedBytes) { (result) in
do
{
let data = try self.process(data: data, error: error)
let data = try result.get()
let response = try AltStoreCore.JSONDecoder().decode(ServerResponse.self, from: data)
completionHandler(.success(response))
@@ -111,36 +108,3 @@ class ServerConnection
}
}
}
private extension ServerConnection
{
func process(data: Data?, error: NWError?) throws -> Data
{
do
{
do
{
guard let data = data else { throw error ?? ALTServerError(.unknown) }
return data
}
catch let error as NWError
{
print("Error receiving data from connection \(connection)", error)
throw ALTServerError(.lostConnection)
}
catch
{
throw error
}
}
catch let error as ALTServerError
{
throw error
}
catch
{
preconditionFailure("A non-ALTServerError should never be thrown from this method.")
}
}
}

View File

@@ -63,39 +63,23 @@ extension ServerManager
func connect(to server: Server, completion: @escaping (Result<ServerConnection, Error>) -> Void)
{
DispatchQueue.global().async {
func finish(_ result: Result<ServerConnection, Error>)
func finish(_ result: Result<Connection, Error>)
{
completion(result)
}
func start(_ connection: NWConnection)
{
connection.stateUpdateHandler = { [unowned connection] (state) in
switch state
{
case .failed(let error):
print("Failed to connect to service \(server.service?.name ?? "").", error)
finish(.failure(ConnectionError.connectionFailed))
case .cancelled:
finish(.failure(OperationError.cancelled))
case .ready:
let connection = ServerConnection(server: server, connection: connection)
finish(.success(connection))
case .waiting: break
case .setup: break
case .preparing: break
@unknown default: break
}
switch result
{
case .failure(let error): completion(.failure(error))
case .success(let connection):
let serverConnection = ServerConnection(server: server, connection: connection)
completion(.success(serverConnection))
}
connection.start(queue: self.dispatchQueue)
}
if let incomingConnectionsSemaphore = self.incomingConnectionsSemaphore, server.connectionType != .wireless
switch server.connectionType
{
case .local: self.connectToLocalServer(completion: finish(_:))
case .wired:
guard let incomingConnectionsSemaphore = self.incomingConnectionsSemaphore else { return finish(.failure(ALTServerError(.connectionFailed))) }
print("Waiting for incoming connection...")
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
@@ -103,39 +87,27 @@ extension ServerManager
switch server.connectionType
{
case .wired: CFNotificationCenterPostNotification(notificationCenter, .wiredServerConnectionStartRequest, nil, nil, true)
case .local: CFNotificationCenterPostNotification(notificationCenter, .localServerConnectionStartRequest, nil, nil, true)
case .wireless: break
case .local, .wireless: break
}
_ = incomingConnectionsSemaphore.wait(timeout: .now() + 10.0)
if let connection = self.incomingConnections?.popLast()
{
start(connection)
self.connectToRemoteServer(server, connection: connection, completion: finish(_:))
}
else
{
finish(.failure(ALTServerError(.connectionFailed)))
}
}
else if let service = server.service
{
case .wireless:
guard let service = server.service else { return finish(.failure(ALTServerError(.connectionFailed))) }
print("Connecting to service:", service)
let parameters = NWParameters.tcp
if server.connectionType == .local
{
// Prevent AltStore from initiating connections over multiple interfaces simultaneously 🤷
parameters.requiredInterfaceType = .loopback
}
let connection = NWConnection(to: .service(name: service.name, type: service.type, domain: service.domain, interface: nil), using: parameters)
start(connection)
}
else
{
finish(.failure(ALTServerError(.connectionFailed)))
let connection = NWConnection(to: .service(name: service.name, type: service.type, domain: service.domain, interface: nil), using: .tcp)
self.connectToRemoteServer(server, connection: connection, completion: finish(_:))
}
}
}
@@ -191,6 +163,47 @@ private extension ServerManager
self.incomingConnections = nil
self.incomingConnectionsSemaphore = nil
}
func connectToRemoteServer(_ server: Server, connection: NWConnection, completion: @escaping (Result<Connection, Error>) -> Void)
{
connection.stateUpdateHandler = { [unowned connection] (state) in
switch state
{
case .failed(let error):
print("Failed to connect to service \(server.service?.name ?? "").", error)
completion(.failure(ConnectionError.connectionFailed))
case .cancelled:
completion(.failure(OperationError.cancelled))
case .ready:
let connection = NetworkConnection(connection)
completion(.success(connection))
case .waiting: break
case .setup: break
case .preparing: break
@unknown default: break
}
}
connection.start(queue: self.dispatchQueue)
}
func connectToLocalServer(completion: @escaping (Result<Connection, Error>) -> Void)
{
let connection = XPCConnection()
connection.connect { (result) in
switch result
{
case .failure(let error):
print("Could not connect to AltDaemon XPC service.", error)
completion(.failure(error))
case .success: completion(.success(connection))
}
}
}
}
extension ServerManager: NetServiceBrowserDelegate