1

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 .local retornavam No 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/12 e 192.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

  1. NSLocalNetworkUsageDescription continua obrigatório; a conexão no processo apenas complementa a configuração.
  2. O gatilho precisa acontecer antes do ponto comum de todos os fluxos reais, incluindo terminal, arquivos e monitoramento.
  3. É necessário cancelar rapidamente. Enquanto a decisão do usuário está pendente, a conexão pode ficar em preparing ou waiting.
  4. No route to host nã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, route ou traceroute mostram 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.

Carregando publicação patrocinada...