Skip to content

Commit 0c7bac3

Browse files
authored
fix(ios): classify TLS fingerprint timeouts (#98429)
* fix(ios): classify gateway TLS fingerprint timeouts * Add discovered TLS trust regression test
1 parent fc5ba0e commit 0c7bac3

7 files changed

Lines changed: 456 additions & 88 deletions

apps/ios/Sources/Design/SettingsProTabActions.swift

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,11 @@ extension SettingsProTab {
188188
self.setupStatusText = nil
189189
guard self.applySetupCode() else { return }
190190
let host = self.manualGatewayHost.trimmingCharacters(in: .whitespacesAndNewlines)
191-
guard let port = self.resolvedManualPort(host: host) else {
191+
guard self.resolvedManualPort(host: host) != nil else {
192192
self.setupStatusText = "Failed: invalid port"
193193
return
194194
}
195-
guard await self.preflightGateway(host: host, port: port) else { return }
195+
guard await self.preflightGateway(host: host) else { return }
196196
self.setupStatusText = "Setup code applied. Connecting..."
197197
await self.connectManual()
198198
}
@@ -286,11 +286,11 @@ extension SettingsProTab {
286286

287287
func connectAfterScannedGatewayLink() async {
288288
let host = self.manualGatewayHost.trimmingCharacters(in: .whitespacesAndNewlines)
289-
guard let port = self.resolvedManualPort(host: host) else {
289+
guard self.resolvedManualPort(host: host) != nil else {
290290
self.setupStatusText = "Failed: invalid port"
291291
return
292292
}
293-
guard await self.preflightGateway(host: host, port: port) else { return }
293+
guard await self.preflightGateway(host: host) else { return }
294294
await self.connectManual()
295295
}
296296

@@ -319,20 +319,15 @@ extension SettingsProTab {
319319
authOverride: authOverride)
320320
}
321321

322-
func preflightGateway(host: String, port: Int) async -> Bool {
322+
func preflightGateway(host: String) async -> Bool {
323323
let trimmed = host.trimmingCharacters(in: .whitespacesAndNewlines)
324324
guard !trimmed.isEmpty else { return false }
325325
if Self.isTailnetHostOrIP(trimmed), !Self.hasTailnetIPv4() {
326326
self.setupStatusText = "Tailscale is off on this device. Turn it on, then try again."
327327
return false
328328
}
329329
self.gatewayController.requestLocalNetworkAccess(reason: "settings_preflight")
330-
self.setupStatusText = "Checking gateway reachability..."
331-
let ok = await TCPProbe.probe(host: trimmed, port: port, timeoutSeconds: 3, queueLabel: "gateway.preflight")
332-
if !ok {
333-
self.setupStatusText = "Can't reach gateway at \(trimmed):\(port). Check Tailscale or LAN."
334-
}
335-
return ok
330+
return true
336331
}
337332

338333
func resetOnboarding() {
@@ -562,6 +557,12 @@ extension SettingsProTab {
562557
let gatewayStatus = self.appModel.gatewayStatusText.trimmingCharacters(in: .whitespacesAndNewlines)
563558
if let friendly = self.friendlyGatewayMessage(from: gatewayStatus) { return friendly }
564559
if let friendly = self.friendlyGatewayMessage(from: trimmedSetup) { return friendly }
560+
if self.isTransientSetupStatus(trimmedSetup),
561+
!gatewayStatus.isEmpty,
562+
gatewayStatus != "Offline"
563+
{
564+
return gatewayStatus
565+
}
565566
if !trimmedSetup.isEmpty { return trimmedSetup }
566567
if gatewayStatus.isEmpty || gatewayStatus == "Offline" { return nil }
567568
return gatewayStatus
@@ -586,6 +587,11 @@ extension SettingsProTab {
586587
if lower.contains("device nonce required") || lower.contains("device nonce mismatch") {
587588
return "Secure handshake failed. Check Tailscale, then connect again."
588589
}
590+
if lower.contains("tls fingerprint verification timed out")
591+
|| lower.contains("no tls endpoint detected")
592+
{
593+
return raw.trimmingCharacters(in: .whitespacesAndNewlines)
594+
}
589595
if lower.contains("timed out") {
590596
return "Connection timed out. Make sure Tailscale is connected, then try again."
591597
}
@@ -595,6 +601,13 @@ extension SettingsProTab {
595601
return nil
596602
}
597603

604+
func isTransientSetupStatus(_ raw: String) -> Bool {
605+
let lower = raw.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
606+
return lower == "setup code applied. connecting..."
607+
|| lower.hasPrefix("qr loaded. connecting to ")
608+
|| lower == "checking gateway reachability..."
609+
}
610+
598611
var shouldShowRealtimeVoicePicker: Bool {
599612
let providerSelection = TalkModeProviderSelection.resolved(self.talkProviderSelectionRaw)
600613
return providerSelection == .openAIRealtime || self.appModel.talkMode.gatewayTalkUsesRealtime

0 commit comments

Comments
 (0)