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 } } }