krz/domain-dig

an ios app for DNS & SSL analysis

clone: git clone https://gitbay.org/krz/domain-dig.git

v4.7.0: DomainDig/ReachabilityService.swift · raw

 1import Foundation
 2import Network
 3
 4struct ReachabilityService {
 5    static func check(domain: String, port: UInt16) async -> PortReachability {
 6        await withCheckedContinuation { continuation in
 7            let host = NWEndpoint.Host(domain)
 8            let nwPort = NWEndpoint.Port(rawValue: port)!
 9            let connection = NWConnection(host: host, port: nwPort, using: .tcp)
10            let context = ConnectionContext(port: port, connection: connection, continuation: continuation)
11
12            connection.stateUpdateHandler = { state in
13                switch state {
14                case .ready:
15                    context.finish(reachable: true)
16                case .failed, .cancelled:
17                    context.finish(reachable: false)
18                default:
19                    break
20                }
21            }
22
23            let queue = DispatchQueue(label: "reachability.\(port)")
24            connection.start(queue: queue)
25
26            queue.asyncAfter(deadline: .now() + 2) {
27                context.finish(reachable: false)
28            }
29        }
30    }
31
32    static func checkAll(domain: String) async -> ServiceResult<[PortReachability]> {
33        async let port443 = check(domain: domain, port: 443)
34        async let port80 = check(domain: domain, port: 80)
35        let results = await [port443, port80]
36        return results.isEmpty ? .empty("No reachability results") : .success(results)
37    }
38}
39
40private final class ConnectionContext: @unchecked Sendable {
41    private let port: UInt16
42    private let connection: NWConnection
43    private let continuation: CheckedContinuation<PortReachability, Never>
44    private let start = CFAbsoluteTimeGetCurrent()
45    private let lock = NSLock()
46    private nonisolated(unsafe) var resumed = false
47
48    init(port: UInt16, connection: NWConnection, continuation: CheckedContinuation<PortReachability, Never>) {
49        self.port = port
50        self.connection = connection
51        self.continuation = continuation
52    }
53
54    nonisolated func finish(reachable: Bool) {
55        lock.lock()
56        guard !resumed else {
57            lock.unlock()
58            return
59        }
60        resumed = true
61        lock.unlock()
62
63        let elapsed = CFAbsoluteTimeGetCurrent() - start
64        let ms = reachable ? Int(elapsed * 1000) : nil
65        connection.cancel()
66        continuation.resume(returning: PortReachability(port: port, reachable: reachable, latencyMs: ms))
67    }
68}