Contornando Bloqueios em um Aplicativo iOS: VLESS + Reality via sing-box e os Obstáculos no Caminho
Um relato técnico sobre como a equipe do mensageiro RCQ implementou a tecnologia VLESS + Reality com sing-box para contornar bloqueios em seu aplicativo iOS. O artigo detalha as escolhas de design, os desafios de infraestrutura e as lições aprendidas, focando em como manter o aplicativo funcional em ambientes com censura.
MundiX News·23 de maio de 2026·10 min de leitura·👁 8 views
rcq
19 minutos atrás
Contornando Bloqueios em um Aplicativo iOS: VLESS + Reality via sing-box e os Obstáculos no Caminho
Médio
6 min
499
Swift
*
Go
*
Segurança da Informação
*
Tecnologias de Rede
*
iOS
*
Caso de Uso
Da Caixa de Areia
Nós desenvolvemos um mensageiro. Na primavera de 2026, nosso backend começou a falhar para alguns usuários na Rússia: as requisições HTTPS para a API expiravam, o WebSocket não subia. A situação é familiar para todos que mantêm um serviço com um único domínio e um único IP.
Para um mensageiro, isso é uma sentença de morte. Não é “inconveniente”, é uma sentença de morte: um aplicativo que nem consegue se conectar é inútil. E a opção “peça ao usuário para primeiro ativar a VPN” não nos agradava nem um pouco. Abaixo, detalharei por que acabamos integrando o contorno diretamente no aplicativo, em que ele funciona e em quais obstáculos tropeçamos. Sem marketing, direto ao ponto.
Por que não “apenas VPN”
A primeira ideia de todos é a mesma: que o usuário instale uma VPN. Mas, se você olhar para isso com os olhos do produto, tudo desmorona.
Primeiro, isso mata o funil. Cada etapa adicional até que “o aplicativo funcione” custa uma parte da sua audiência, e “instale e configure um aplicativo separado” não é uma etapa, é um abismo. É especialmente doloroso que a parte dos usuários que mais precisa do produto seja exatamente a que falha.
Segundo, a própria VPN é um alvo móvel. Protocolos populares (OpenVPN, WireGuard, IKEv2) são detectados há muito tempo e bem por assinaturas, os intervalos de provedores de VPN conhecidos são cortados em massa. Ou seja, você delega uma função crítica para você a um aplicativo de terceiros, que amanhã pode parar de funcionar por conta própria.
Queríamos um comportamento diferente: o usuário abre o mensageiro e ele funciona. Sem um aplicativo separado, sem uma assinatura de um serviço de terceiros, sem um perfil de VPN nas configurações do sistema iOS. O contorno deve ser um detalhe de implementação, e não uma tarefa do usuário.
O que exatamente está sendo bloqueado
Para entender o que consertar, você precisa entender o que está quebrando. Temos um esquema bastante típico: API HTTPS e WebSocket no mesmo domínio. Bloquear essa conexão pode ser feito de várias maneiras, e geralmente elas são combinadas:
Por endereço IP do servidor;
Por nome de domínio em ClientHello (o campo SNI é enviado em texto simples, mesmo em TLS);
Por DPI, que olha para a própria estrutura do tráfego e reconhece o protocolo.
Uma conexão TLS normal para o seu domínio é cortada por SNI em um instante. Portanto, a tarefa é formulada da seguinte forma: a conexão deve parecer uma chamada para algum outro host, sem nada de especial e obviamente permitido. Não “criptografar mais”, mas sim “parecer diferente”.
Por que VLESS e Reality
Um pouco de história, é importante aqui. Shadowsocks e VMess, em seu tempo, resolveram exatamente essa tarefa, e por algum tempo resolveram bem. O problema é a sondagem ativa: o censor não apenas observa seu tráfego passivamente, ele mesmo envia uma solicitação de teste para um servidor suspeito. Se o servidor responder como um proxy (e as primeiras implementações responderam de forma reconhecível), o endereço vai para o bloco. Além disso, assinaturas passivas foram acumuladas, pelas quais o “tráfego de proxy” diferia do HTTPS normal.
Reality resolve isso com um truque cuidadoso. O servidor proxy não apresenta seu próprio certificado TLS. Em vez disso, durante o handshake, ele faz proxy do TLS-handshake para um site real, de terceiros e popular. Para um observador passivo e para um testador ativo, seu servidor é indistinguível desse site: certificado válido, handshake válido, cadeia válida. E somente o cliente, que possui a chave pública correta, após o handshake “muda” para o túnel real. A sondagem de terceiros não tem para que mudar, ela vê um site grande normal.
o que isso dá na prática:
Não há seu próprio domínio, que pode ser inserido no bloco por SNI;
Não há seu próprio certificado, pelo qual você pode identificar “este é um proxy”;
A sondagem ativa se baseia em um site legítimo de terceiros.
VLESS aqui é um transporte leve sem sua própria camada de criptografia (a criptografia é assumida pelo TLS), com uma impressão digital pequena e sem expressão. E o fluxo xtls-rprx-vision suaviza ainda mais o padrão “TLS dentro de TLS”, que, caso contrário, é visível pelos tamanhos característicos dos pacotes.
Não inventamos nada disso. Reality e sing-box são trabalho de outras pessoas, e muito bom. A parte interessante começou mais tarde: como colocar isso dentro de um aplicativo normal para que o usuário não percebesse nada.
sing-box dentro do aplicativo
sing-box é uma plataforma proxy universal em Go e, entre outras coisas, pode ser um cliente VLESS + Reality. Normalmente, ele é executado como um processo separado com uma configuração. Não precisamos de um processo separado, precisamos que ele viva dentro do aplicativo iOS.
Isso é ajudado pelo gomobile. Através do
gomobile bind
O código Go é compilado em nativo
.xcframework
, que é vinculado ao aplicativo como uma dependência normal. Ou seja, sing-box não é um CLI próximo a nós, mas um framework dentro do binário. Executamos diretamente no processo do aplicativo.
sing-box, iniciado dessa forma, abre um inbound local (tipo mixed, ou seja, SOCKS e HTTP ao mesmo tempo) em
127.0.0.1
em uma porta aleatória. Em seguida, há uma bifurcação, e ela é fundamental.
Você pode fazer uma VPN do sistema via
NEPacketTunnelProvider
: então todo o tráfego do dispositivo passará pelo túnel. Você pode fazer um proxy no nível do aplicativo: então apenas o tráfego do seu aplicativo passará pelo túnel. Escolhemos a segunda opção, e é por isso. Não precisamos direcionar todo o telefone pelo relay, precisamos entregar apenas o tráfego do mensageiro ao servidor. E como é assim, então a Network Extension com sua arquitetura, um processo de extensão separado, limites de memória e perguntas adicionais na revisão na App Store, simplesmente não precisamos. Menos peças móveis, menos pontos de falha.
Na prática, isso se parece com o seguinte: sing-box executa um proxy local, e a pilha de rede do aplicativo (
URLSession
) envolvemos nesse endereço local.
swift
let config =URLSessionConfiguration.defaultconfig.connectionProxyDictionary =["SOCKSEnable":1,"SOCKSProxy":"127.0.0.1","SOCKSPort": localPort,]
Todo o resto do telefone não é afetado, nenhum perfil de VPN aparece nas configurações. A desvantagem da abordagem é honesta: esta não é uma VPN universal, outros aplicativos não passarão por ela. Mas não precisávamos disso.
A configuração para sing-box é gerada em tempo de execução. Se você remover o desnecessário, o núcleo ficará assim:
Separo
utls
com impressão digital
chrome
. A implementação TLS padrão do Go tem seu próprio ClientHello reconhecível, e esta é uma assinatura em si. uTLS substitui ClientHello para que ele corresponda ao Chrome real. O detalhe é pequeno, mas é desses pequenos detalhes que se forma “parece um navegador normal”.
Obstáculos com relay
Agora, pelo que valeu a pena escrever este artigo. Com o protocolo, tudo acabou bem. Passamos pelos obstáculos na infraestrutura.
O primeiro relay foi configurado em um VPS barato de um hoster típico. O endereço queimou rapidamente. Os intervalos de data centers e hospedagem, nos quais historicamente há muitos proxies, são rastreados e cortados, seja preventivamente, seja de forma muito operacional. Reality esconde perfeitamente o protocolo, mas não esconde o fato de que “o tráfego vai para um IP de um intervalo suspeito”.
Transferimos o relay para um endereço no intervalo de um grande provedor de nuvem. E aqui ele vive. A lógica é simples: cortar o espaço de endereço de uma grande nuvem por atacado é caro em termos de danos colaterais, há uma massa de serviços legítimos nos mesmos endereços que ninguém quer quebrar.
A conclusão que tiramos para nós mesmos: com Reality, o ponto fraco não é o protocolo, mas o IP. É o endereço que queima. Portanto, o relay é, por definição, um consumível, e deve ser tratado como um consumível.
O endereço do relay não deve ser embutido no binário
Consequência direta do item anterior. Se o endereço do relay estiver hardcoded no aplicativo, então o IP queimado significa um novo lançamento na App Store e aguardar a revisão. Para um consumível que pode queimar a qualquer momento, isso é inaceitável.
Portanto, os parâmetros do relay (endereço, chaves, SNI) o aplicativo deve receber separadamente de sua compilação. Então, a alteração do relay é uma alteração da configuração, e não um ciclo de lançamento. O mecanismo específico de entrega da configuração pode ser diferente, o princípio em si é importante: o que queima com frequência não vive no binário.
Honestidade sobre limites
O túnel não é mágica, e vendê-lo como mágica é desonesto.
O operador do relay (no nosso caso, nós mesmos) vê seu tráfego para nossos servidores da mesma forma que seu provedor o veria com uma conexão direta. O túnel muda a aparência da conexão para o censor no caminho e não muda quem está nas extremidades. O conteúdo da correspondência é, em qualquer caso, protegido por criptografia de ponta a ponta no nível do aplicativo (libsignal), e o túnel não adiciona nem subtrai nada a isso. É importante separar isso: contornar bloqueios e a privacidade da correspondência são duas tarefas diferentes que são resolvidas por diferentes camadas.
Reality mantém bem a sondagem ativa, mas “para sempre” não existe nesta área. Os IPs queimam, as assinaturas se acumulam, os métodos de detecção se desenvolvem. Esta é uma corrida, e você precisa tratá-la como uma corrida: ter uma reserva de endereços, ser capaz de alterá-los rapidamente, monitorar o que exatamente falhou.
Em nosso caso, o contorno está desativado por padrão. O aplicativo primeiro tenta se conectar diretamente, e somente se a conexão direta não for bem-sucedida, ele levanta o túnel. Não faz sentido direcionar o tráfego pelo relay onde a rede já está aberta.
O que tirar deste texto
Se você estiver resolvendo um problema semelhante, três coisas que economizarão seu tempo:
Um proxy no nível do aplicativo geralmente é melhor do que uma VPN do sistema. Se você só precisa entregar seu tráfego ao servidor,
NEPacketTunnelProvider
é uma complexidade desnecessária. Um inbound local mais
connectionProxyDictionary
resolve o problema com menos esforço.
O protocolo é a parte fácil. VLESS + Reality + sing-box é um problema resolvido, tudo já foi escrito antes de você. A parte difícil é a infraestrutura: quais endereços você usa, com que rapidez você os altera, como você monitora tudo isso.
Planeje a rotação desde o primeiro dia. Não “algum dia colocaremos o relay na configuração”, mas imediatamente. Porque o primeiro IP queimado mostrará se você tem rotação ou não, e descobrir isso na produção é desagradável.
Tudo o que foi descrito vive em nosso mensageiro RCQ, agora está em beta aberto no iOS. O cliente para iOS é de código aberto, então, se desejar, você pode ver exatamente como o trabalho com o transporte é organizado, e não acreditar na palavra:
github.com/rcq-messenger/rcq-ios
Se você está fazendo algo semelhante e encontrou seus obstáculos na infraestrutura, conte nos comentários, especialmente interessante sobre a prática de rotação de endereços.
Tags:
contorno de bloqueios
VLESS
Reality
sing-box
XTLS
proxy
censura
iOS
Swift
gomobile
Hashes:
Swift
Go
Segurança da Informação
Tecnologias de Rede
iOS
rcq
19 minutos atrás
Contornando Bloqueios em um Aplicativo iOS: VLESS + Reality via sing-box e os Obstáculos no Caminho
Médio
6 min
499
Swift
*
Go
*
Segurança da Informação
*
Tecnologias de Rede
*
iOS
*
Caso de Uso
Da Caixa de Areia
Nós desenvolvemos um mensageiro. Na primavera de 2026, nosso backend começou a falhar para alguns usuários na Rússia: as requisições HTTPS para a API expiravam, o WebSocket não subia. A situação é familiar para todos que mantêm um serviço com um único domínio e um único IP.
Para um mensageiro, isso é uma sentença de morte. Não é “inconveniente”, é uma sentença de morte: um aplicativo que nem consegue se conectar é inútil. E a opção “peça ao usuário para primeiro ativar a VPN” não nos agradava nem um pouco. Abaixo, detalharei por que acabamos integrando o contorno diretamente no aplicativo, em que ele funciona e em quais obstáculos tropeçamos. Sem marketing, direto ao ponto.
Por que não “apenas VPN”
A primeira ideia de todos é a mesma: que o usuário instale uma VPN. Mas, se você olhar para isso com os olhos do produto, tudo desmorona.
Primeiro, isso mata o funil. Cada etapa adicional até que “o aplicativo funcione” custa uma parte da sua audiência, e “instale e configure um aplicativo separado” não é uma etapa, é um abismo. É especialmente doloroso que a parte dos usuários que mais precisa do produto seja exatamente a que falha.
Segundo, a própria VPN é um alvo móvel. Protocolos populares (OpenVPN, WireGuard, IKEv2) são detectados há muito tempo e bem por assinaturas, os intervalos de provedores de VPN conhecidos são cortados em massa. Ou seja, você delega uma função crítica para você a um aplicativo de terceiros, que amanhã pode parar de funcionar por conta própria.
Queríamos um comportamento diferente: o usuário abre o mensageiro e ele funciona. Sem um aplicativo separado, sem uma assinatura de um serviço de terceiros, sem um perfil de VPN nas configurações do sistema iOS. O contorno deve ser um detalhe de implementação, e não uma tarefa do usuário.
O que exatamente está sendo bloqueado
Para entender o que consertar, você precisa entender o que está quebrando. Temos um esquema bastante típico: API HTTPS e WebSocket no mesmo domínio. Bloquear essa conexão pode ser feito de várias maneiras, e geralmente elas são combinadas:
Por endereço IP do servidor;
Por nome de domínio em ClientHello (o campo SNI é enviado em texto simples, mesmo em TLS);
Por DPI, que olha para a própria estrutura do tráfego e reconhece o protocolo.
Uma conexão TLS normal para o seu domínio é cortada por SNI em um instante. Portanto, a tarefa é formulada da seguinte forma: a conexão deve parecer uma chamada para algum outro host, sem nada de especial e obviamente permitido. Não “criptografar mais”, mas sim “parecer diferente”.
Por que VLESS e Reality
Um pouco de história, é importante aqui. Shadowsocks e VMess, em seu tempo, resolveram exatamente essa tarefa, e por algum tempo resolveram bem. O problema é a sondagem ativa: o censor não apenas observa seu tráfego passivamente, ele mesmo envia uma solicitação de teste para um servidor suspeito. Se o servidor responder como um proxy (e as primeiras implementações responderam de forma reconhecível), o endereço vai para o bloco. Além disso, assinaturas passivas foram acumuladas, pelas quais o “tráfego de proxy” diferia do HTTPS normal.
Reality resolve isso com um truque cuidadoso. O servidor proxy não apresenta seu próprio certificado TLS. Em vez disso, durante o handshake, ele faz proxy do TLS-handshake para um site real, de terceiros e popular. Para um observador passivo e para um testador ativo, seu servidor é indistinguível desse site: certificado válido, handshake válido, cadeia válida. E somente o cliente, que possui a chave pública correta, após o handshake “muda” para o túnel real. A sondagem de terceiros não tem para que mudar, ela vê um site grande normal.
o que isso dá na prática:
Não há seu próprio domínio, que pode ser inserido no bloco por SNI;
Não há seu próprio certificado, pelo qual você pode identificar “este é um proxy”;
A sondagem ativa se baseia em um site legítimo de terceiros.
VLESS aqui é um transporte leve sem sua própria camada de criptografia (a criptografia é assumida pelo TLS), com uma impressão digital pequena e sem expressão. E o fluxo xtls-rprx-vision suaviza ainda mais o padrão “TLS dentro de TLS”, que, caso contrário, é visível pelos tamanhos característicos dos pacotes.
Não inventamos nada disso. Reality e sing-box são trabalho de outras pessoas, e muito bom. A parte interessante começou mais tarde: como colocar isso dentro de um aplicativo normal para que o usuário não percebesse nada.
sing-box dentro do aplicativo
sing-box é uma plataforma proxy universal em Go e, entre outras coisas, pode ser um cliente VLESS + Reality. Normalmente, ele é executado como um processo separado com uma configuração. Não precisamos de um processo separado, precisamos que ele viva dentro do aplicativo iOS.
Isso é ajudado pelo gomobile. Através do
gomobile bind
O código Go é compilado em nativo
.xcframework
, que é vinculado ao aplicativo como uma dependência normal. Ou seja, sing-box não é um CLI próximo a nós, mas um framework dentro do binário. Executamos diretamente no processo do aplicativo.
sing-box, iniciado dessa forma, abre um inbound local (tipo mixed, ou seja, SOCKS e HTTP ao mesmo tempo) em
127.0.0.1
em uma porta aleatória. Em seguida, há uma bifurcação, e ela é fundamental.
Você pode fazer uma VPN do sistema via
NEPacketTunnelProvider
: então todo o tráfego do dispositivo passará pelo túnel. Você pode fazer um proxy no nível do aplicativo: então apenas o tráfego do seu aplicativo passará pelo túnel. Escolhemos a segunda opção, e é por isso. Não precisamos direcionar todo o telefone pelo relay, precisamos entregar apenas o tráfego do mensageiro ao servidor. E como é assim, então a Network Extension com sua arquitetura, um processo de extensão separado, limites de memória e perguntas adicionais na revisão na App Store, simplesmente não precisamos. Menos peças móveis, menos pontos de falha.
Na prática, isso se parece com o seguinte: sing-box executa um proxy local, e a pilha de rede do aplicativo (
URLSession
) envolvemos nesse endereço local.
Todo o resto do telefone não é afetado, nenhum perfil de VPN aparece nas configurações. A desvantagem da abordagem é honesta: esta não é uma VPN universal, outros aplicativos não passarão por ela. Mas não precisávamos disso.
A configuração para sing-box é gerada em tempo de execução. Se você remover o desnecessário, o núcleo ficará assim:
Separo
utls
com impressão digital
chrome
. A implementação TLS padrão do Go tem seu próprio ClientHello reconhecível, e esta é uma assinatura em si. uTLS substitui ClientHello para que ele corresponda ao Chrome real. O detalhe é pequeno, mas é desses pequenos detalhes que se forma “parece um navegador normal”.
Obstáculos com relay
Agora, pelo que valeu a pena escrever este artigo. Com o protocolo, tudo acabou bem. Passamos pelos obstáculos na infraestrutura.
O primeiro relay foi configurado em um VPS barato de um hoster típico. O endereço queimou rapidamente. Os intervalos de data centers e hospedagem, nos quais historicamente há muitos proxies, são rastreados e cortados, seja preventivamente, seja de forma muito operacional. Reality esconde perfeitamente o protocolo, mas não esconde o fato de que “o tráfego vai para um IP de um intervalo suspeito”.
Transferimos o relay para um endereço no intervalo de um grande provedor de nuvem. E aqui ele vive. A lógica é simples: cortar o espaço de endereço de uma grande nuvem por atacado é caro em termos de danos colaterais, há uma massa de serviços legítimos nos mesmos endereços que ninguém quer quebrar.
A conclusão que tiramos para nós mesmos: com Reality, o ponto fraco não é o protocolo, mas o IP. É o endereço que queima. Portanto, o relay é, por definição, um consumível, e deve ser tratado como um consumível.
O endereço do relay não deve ser embutido no binário
Consequência direta do item anterior. Se o endereço do relay estiver hardcoded no aplicativo, então o IP queimado significa um novo lançamento na App Store e aguardar a revisão. Para um consumível que pode queimar a qualquer momento, isso é inaceitável.
Portanto, os parâmetros do relay (endereço, chaves, SNI) o aplicativo deve receber separadamente de sua compilação. Então, a alteração do relay é uma alteração da configuração, e não um ciclo de lançamento. O mecanismo específico de entrega da configuração pode ser diferente, o princípio em si é importante: o que queima com frequência não vive no binário.
Honestidade sobre limites
O túnel não é mágica, e vendê-lo como mágica é desonesto.
O operador do relay (no nosso caso, nós mesmos) vê seu tráfego para nossos servidores da mesma forma que seu provedor o veria com uma conexão direta. O túnel muda a aparência da conexão para o censor no caminho e não muda quem está nas extremidades. O conteúdo da correspondência é, em qualquer caso, protegido por criptografia de ponta a ponta no nível do aplicativo (libsignal), e o túnel não adiciona nem subtrai nada a isso. É importante separar isso: contornar bloqueios e a privacidade da correspondência são duas tarefas diferentes que são resolvidas por diferentes camadas.
Reality mantém bem a sondagem ativa, mas “para sempre” não existe nesta área. Os IPs queimam, as assinaturas se acumulam, os métodos de detecção se desenvolvem. Esta é uma corrida, e você precisa tratá-la como uma corrida: ter uma reserva de endereços, ser capaz de alterá-los rapidamente, monitorar o que exatamente falhou.
Em nosso caso, o contorno está desativado por padrão. O aplicativo primeiro tenta se conectar diretamente, e somente se a conexão direta não for bem-sucedida, ele levanta o túnel. Não faz sentido direcionar o tráfego pelo relay onde a rede já está aberta.
O que tirar deste texto
Se você estiver resolvendo um problema semelhante, três coisas que economizarão seu tempo:
Um proxy no nível do aplicativo geralmente é melhor do que uma VPN do sistema. Se você só precisa entregar seu tráfego ao servidor,
NEPacketTunnelProvider
é uma complexidade desnecessária. Um inbound local mais
connectionProxyDictionary
resolve o problema com menos esforço.
O protocolo é a parte fácil. VLESS + Reality + sing-box é um problema resolvido, tudo já foi escrito antes de você. A parte difícil é a infraestrutura: quais endereços você usa, com que rapidez você os altera, como você monitora tudo isso.
Planeje a rotação desde o primeiro dia. Não “algum dia colocaremos o relay na configuração”, mas imediatamente. Porque o primeiro IP queimado mostrará se você tem rotação ou não, e descobrir isso na produção é desagradável.
Tudo o que foi descrito vive em nosso mensageiro RCQ, agora está em beta aberto no iOS. O cliente para iOS é de código aberto, então, se desejar, você pode ver exatamente como o trabalho com o transporte é organizado, e não acreditar na palavra:
github.com/rcq-messenger/rcq-ios
Se você está fazendo algo semelhante e encontrou seus obstáculos na infraestrutura, conte nos comentários, especialmente interessante sobre a prática de rotação de endereços.
Tags:
contorno de bloqueios
VLESS
Reality
sing-box
XTLS
proxy
censura
iOS
Swift
gomobile
Hashes:
Swift
Go
Segurança da Informação
Tecnologias de Rede
iOS