SSH no macOS: quando “No route to host” e a permissão de Rede Local nunca aparece
Durante os testes de um cliente SSH para macOS, encontrei um caso confuso:
- conexões com servidores públicos funcionavam;
- IPs privados e hosts
.localretornavamNo route to host; - o macOS não mostrava o pedido de acesso à Rede Local;
- o aplicativo nem aparecia em Ajustes do Sistema > Privacidade e Segurança > Rede Local.
O Info.plist já continha NSLocalNetworkUsageDescription. O detalhe importante é que essa descrição explica a permissão ao usuário, mas não inicia sozinha a verificação do TCC.
Por que o prompt não apareceu
O fluxo SSH usava o executável ssh do sistema como processo filho. Em alguns ambientes, depender apenas da conexão feita por esse processo não fazia o aplicativo principal aparecer de forma confiável na lista de Rede Local.
A solução prática foi fazer o próprio processo do app tocar brevemente o endpoint local antes de iniciar o fluxo SSH normal:
import Network
func triggerLocalNetworkPermission(host: String, port: UInt16) {
guard let nwPort = NWEndpoint.Port(rawValue: port) else { return }
let connection = NWConnection(
host: NWEndpoint.Host(host),
port: nwPort,
using: .tcp
)
connection.stateUpdateHandler = { state in
switch state {
case .ready, .failed:
connection.cancel()
default:
break
}
}
connection.start(queue: .global(qos: .utility))
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + 3) {
connection.cancel()
}
}
Essa conexão serve somente para acionar a verificação de permissão. O resultado dela não deve decidir se o SSH pode continuar. A conexão real continua responsável por autenticação, verificação da chave do host, sucesso e mensagens de erro.
Não faça uma conexão extra para qualquer destino
Eu limito o gatilho a endereços que podem ser identificados localmente, sem consulta externa:
- IPv4 privados:
10/8,172.16/12e192.168/16; - link-local IPv4:
169.254/16; - nomes mDNS terminados em
.local; - endereços IPv6 link-local e unique-local.
Também mantenho um cache em memória para tocar cada host apenas uma vez por execução do app.
Detalhes que fizeram diferença
NSLocalNetworkUsageDescriptioncontinua obrigatório; a conexão no processo apenas complementa a configuração.- O gatilho precisa acontecer antes do ponto comum de todos os fluxos reais, incluindo terminal, arquivos e monitoramento.
- É necessário cancelar rapidamente. Enquanto a decisão do usuário está pendente, a conexão pode ficar em
preparingouwaiting. No route to hostnão significa automaticamente erro de permissão. VPN, rota, firewall, servidor desligado e rede local também podem gerar a mesma mensagem.
Como diferenciar de um problema de rota
Antes de alterar o código, vale comparar alguns sinais:
- o mesmo destino funciona no Terminal do macOS?
- apenas endereços privados falham dentro do app?
- o app aparece na lista de Rede Local?
- uma VPN está assumindo a rota?
ping,routeoutraceroutemostram um caminho diferente fora do app?
O objetivo não é esconder erros reais de rede, e sim garantir que o macOS tenha a oportunidade de pedir a autorização correta.
Esse caso veio de uma correção real no Nexus Shell v1.6.7, cliente SSH nativo para macOS que eu desenvolvo. Compartilho o diagnóstico porque o mesmo padrão pode afetar outros aplicativos que delegam conexões locais a processos filhos.