krz/domain-dig
an ios app for DNS & SSL analysis
clone: git clone https://gitbay.org/krz/domain-dig.git
v4.4.1: 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}