Make path error handling more granular

pull/293/head
nielsandriesse 4 years ago
parent 04ec67184e
commit 78ce998a8b

@ -137,15 +137,12 @@ public enum OnionRequestAPI {
}
/// Returns a `Path` to be used for building an onion request. Builds new paths as needed.
///
/// - Note: Exposed for testing purposes.
private static func getPath(excluding snode: Snode?) -> Promise<Path> {
guard pathSize >= 1 else { preconditionFailure("Can't build path of size zero.") }
var paths = OnionRequestAPI.paths
if paths.count < pathCount {
let storage = OWSPrimaryStorage.shared()
storage.dbReadConnection.read { transaction in
paths = storage.getOnionRequestPaths(in: transaction)
Storage.read { transaction in
paths = OWSPrimaryStorage.shared().getOnionRequestPaths(in: transaction)
OnionRequestAPI.paths = paths
if paths.count >= pathCount {
guardSnodes.formUnion([ paths[0][0], paths[1][0] ])
@ -172,6 +169,29 @@ public enum OnionRequestAPI {
}
}
private static func dropGuardSnode(_ snode: Snode) {
guardSnodes = guardSnodes.filter { $0 != snode }
}
private static func drop(_ snode: Snode) throws {
var oldPaths = OnionRequestAPI.paths
guard let pathIndex = oldPaths.firstIndex(where: { $0.contains(snode) }) else { return }
var path = oldPaths.remove(at: pathIndex)
guard let snodeIndex = path.firstIndex(of: snode) else { return }
path.remove(at: snodeIndex)
let unusedSnodes = SnodeAPI.snodePool.subtracting(oldPaths.flatMap { $0 })
guard !unusedSnodes.isEmpty else { throw Error.insufficientSnodes }
// randomElement() uses the system's default random generator, which is cryptographically secure
path.append(unusedSnodes.randomElement()!)
// Don't test the new snode as this would reveal the user's IP
let newPaths = oldPaths + [ path ]
OnionRequestAPI.paths = newPaths
try! Storage.writeSync { transaction in
print("[Loki] Persisting onion request paths to database.")
OWSPrimaryStorage.shared().setOnionRequestPaths(newPaths, in: transaction)
}
}
private static func dropAllPaths() {
paths.removeAll()
try! Storage.writeSync { transaction in
@ -179,10 +199,6 @@ public enum OnionRequestAPI {
}
}
private static func dropGuardSnode(_ snode: Snode) {
guardSnodes = guardSnodes.filter { $0 != snode }
}
/// Builds an onion around `payload` and returns the result.
private static func buildOnion(around payload: JSON, targetedAt destination: Destination) -> Promise<OnionBuildingResult> {
var guardSnode: Snode!
@ -335,14 +351,30 @@ public enum OnionRequestAPI {
}
promise.catch2 { error in // Must be invoked on LokiAPI.workQueue
guard case HTTP.Error.httpRequestFailed(let statusCode, let json) = error else { return }
// Marking all the snodes in the path as unreliable here is aggressive, but otherwise users
// can get stuck with a failing path that just refreshes to the same path.
let path = paths.first { $0.contains(guardSnode) }
path?.forEach { snode in
SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode) // Intentionally don't throw
func handleUnspecificError() {
path?.forEach { snode in
SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode) // Intentionally don't throw
}
dropAllPaths()
dropGuardSnode(guardSnode)
}
let prefix = "Next node not found: "
if let message = json?["result"] as? String, message.hasPrefix(prefix) {
let ed25519PublicKey = message.substring(from: prefix.count)
if let path = path, let snode = path.first(where: { $0.publicKeySet?.ed25519Key == ed25519PublicKey }) {
SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode) // Intentionally don't throw
do {
try drop(snode)
} catch {
handleUnspecificError()
}
} else {
handleUnspecificError()
}
} else {
handleUnspecificError()
}
dropAllPaths() // A snode in the path is bad; retry with a different path
dropGuardSnode(guardSnode)
}
return promise
}

Loading…
Cancel
Save