Files

66 lines
2.5 KiB
Swift

import Foundation
import Network
/// Schneller TCP-Vorab-Check, BEVOR ssh gestartet wird.
/// Falscher Port / falscher Host / Firewall → sofort klare Meldung
/// statt „Sitzung beendet (Code 255)" ohne Erklärung.
enum TCPCheck {
private final class Flag {
var done = false
}
/// - completion: nil bei Erfolg, sonst deutsche Fehlermeldung.
static func check(host: String, port: Int, timeout: TimeInterval = 4.0, completion: @escaping (String?) -> Void) {
let h = host.trimmingCharacters(in: .whitespacesAndNewlines)
guard !h.isEmpty else { completion("Kein Host angegeben — Profil bearbeiten."); return }
guard (1...65535).contains(port), let p = NWEndpoint.Port(rawValue: UInt16(port)) else {
completion("Ungültiger Port \(port)."); return
}
let conn = NWConnection(host: NWEndpoint.Host(h), port: p, using: .tcp)
let lock = NSLock()
let flag = Flag()
func finish(_ message: String?) {
lock.lock()
defer { lock.unlock() }
guard !flag.done else { return }
flag.done = true
conn.cancel()
completion(message)
}
conn.stateUpdateHandler = { state in
switch state {
case .ready:
finish(nil)
case .failed(let err):
finish(describe(err, host: h, port: port))
default:
break
}
}
conn.start(queue: .global(qos: .userInitiated))
DispatchQueue.global().asyncAfter(deadline: .now() + timeout) {
finish("Keine Antwort in \(Int(timeout)) s — Hostname/IP, Netzwerk/VPN oder Firewall prüfen.")
}
}
static func describe(_ error: NWError, host: String, port: Int) -> String {
switch error {
case .posix(let code):
switch code.rawValue {
case 61:
return "Port \(port) auf \(host) verweigert die Verbindung — läuft SSH dort? Stimmt der Port?"
case 51, 64, 65:
return "Host \(host) nicht erreichbar — Netzwerk/VPN prüfen."
case 60:
return "Zeitüberschreitung — Host/Port/Firewall prüfen."
default:
return "Netzwerkfehler (\(code.rawValue)): \(error.localizedDescription)"
}
case .dns:
return "Hostname \(host) kann nicht aufgelöst werden — Tippfehler? DNS/VPN prüfen."
default:
return error.localizedDescription
}
}
}