krz/domain-dig

an ios app for DNS & SSL analysis

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

v1.4.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() + 5) {
27                context.finish(reachable: false)
28            }
29        }
30    }
31
32    static func checkAll(domain: String) async -> [PortReachability] {
33        async let port443 = check(domain: domain, port: 443)
34        async let port80 = check(domain: domain, port: 80)
35        return await [port443, port80]
36    }
37}
38
39private final class ConnectionContext: @unchecked Sendable {
40    private let port: UInt16
41    private let connection: NWConnection
42    private let continuation: CheckedContinuation<PortReachability, Never>
43    private let start = CFAbsoluteTimeGetCurrent()
44    private let lock = NSLock()
45    private nonisolated(unsafe) var resumed = false
46
47    init(port: UInt16, connection: NWConnection, continuation: CheckedContinuation<PortReachability, Never>) {
48        self.port = port
49        self.connection = connection
50        self.continuation = continuation
51    }
52
53    nonisolated func finish(reachable: Bool) {
54        lock.lock()
55        guard !resumed else {
56            lock.unlock()
57            return
58        }
59        resumed = true
60        lock.unlock()
61
62        let elapsed = CFAbsoluteTimeGetCurrent() - start
63        let ms = reachable ? Int(elapsed * 1000) : nil
64        connection.cancel()
65        continuation.resume(returning: PortReachability(port: port, reachable: reachable, latencyMs: ms))
66    }
67}