Skip to content
אצלי
Go back

ADSBExchange: enviando dados dos aviões para internet

Mapa web com aeronaves ao vivo e o painel de detalhes de um Boeing 787-9 em rota GRU-AMS

Sétimo artigo da série. Até aqui o receptor era meu e os dados ficavam em casa: montei o pipeline, coloquei o mapa no ar e documentei todas as formas de olhar para os dados. Na parte 4 eu escrevi, com todas as letras, que deixava o feed externo desligado de propósito, sem compartilhar dados nem posição com a rede ADSBExchange.

Mudei de ideia. Este artigo é sobre o que eu aprendi no caminho, e por que a decisão é menos óbvia do que parece.

O que é o ADSBExchange

É um agregador. Milhares de pessoas com receptores caseiros, como o que montei nesta série, enviam o que captam para um servidor central, que junta tudo num mapa global. Sozinho, o meu receptor enxerga um raio de algumas centenas de quilômetros em volta de Guarulhos. Somado a milhares de outros, vira cobertura mundial.

Existem vários agregadores assim. FlightRadar24, FlightAware, Plane Finder, ADSB.lol, airplanes.live. O que separava o ADSBExchange dos grandes não era tecnologia mas sim a política de dados.

O mapa deles, o globe.adsbexchange.com, na região que o meu receptor cobre. Os 31 aviões na tela aqui são parte dos mais de 12 mil que a rede inteira estava vendo naquele momento:

Mapa global do ADSBExchange sobre a região metropolitana de São Paulo, com dezenas de aeronaves e o painel de detalhes de um Embraer E195 da Azul

O diferencial: não filtrar

Aeronave transmite ADS-B em texto aberto, sem autenticação, como expliquei no segundo artigo. Qualquer pessoa com uma antena capta. Só que o que uma rede decide fazer com esses dados é outra história.

Nos Estados Unidos existe um programa da FAA chamado LADD, que permite a proprietários de aeronaves pedir que seus voos não sejam exibidos publicamente. FlightRadar24 e FlightAware honram esses pedidos, e também removem aeronaves mediante solicitação. O resultado é um mapa onde o jatinho executivo de quem tem advogado simplesmente não aparece.

O ADSBExchange construiu a reputação inteira em cima de recusar isso. A tese era simples: o sinal é público, foi captado por voluntários com equipamento próprio, e filtrar a pedido de quem tem dinheiro transforma uma ferramenta de transparência em serviço de conveniência para poucos. Foi por isso que a rede virou a fonte preferida de jornalistas e pesquisadores que rastreiam jatos de oligarcas, políticos e bilionários, incluindo a conta que acompanhava o avião do Elon Musk depois que o Twitter a baniu.

Esse é o argumento que sempre me pareceu forte. Ele é, inclusive, o mesmo argumento que uso quando escrevo sobre vigilância aqui no blog, só que apontado na direção contrária do usual: transparência para quem tem poder, privacidade para quem não tem.

A venda para a JETNET, em 2023

Aqui a história complica: Em janeiro de 2023, o fundador Dan Streufert vendeu o ADSBExchange para a JETNET, uma empresa de inteligência de dados de aviação, por um valor estimado em torno de 20 milhões de dólares. A comunidade reagiu muito mal, e por três motivos distintos.

O primeiro foi de princípio: a rede era construída por voluntários não remunerados, que doavam hardware, energia elétrica e banda, e o valor acumulado por esse trabalho coletivo foi capitalizado por uma pessoa. Colaboradores do próprio projeto, incluindo gente que escreveu código, reclamaram publicamente de não terem recebido nada.

O segundo foi sobre os dados históricos, vendidos junto sem que quem os produziu fosse consultado.

O terceiro, e o mais relevante para o futuro, foi o receio óbvio: uma empresa que vende inteligência de aviação para clientes corporativos tem incentivo comercial que uma comunidade não tem. E o principal ativo do ADSBExchange sempre foi a promessa de não filtrar. Se essa promessa cair, sobra um agregador igual aos outros, só que construído de graça.

Reação houve, e migração também. Projetos como o ADSB.lol e o airplanes.live nasceram ou cresceram justamente como alternativas comunitárias depois disso.

Então por que enviar

Porque a alternativa que eu vinha praticando, que era não contribuir com ninguém, não é neutra. Ela só significa que eu uso a cobertura construída por outras pessoas, quando abro qualquer site de rastreamento, sem devolver nada. Meu receptor cobre uma região densa, embaixo de uma das rotas mais movimentadas da América Latina, e essa cobertura tem valor real para o mapa coletivo.

A tese de não filtrar continua sendo a mais alinhada com o que eu defendo, e enquanto ela se sustentar na prática, contribuir faz sentido. Se um dia mudar, desligar é um comando só, e eu escrevo sobre isso aqui. Vale dizer que essa não é a única escolha possível, e nem estou dizendo que é a certa para você. Alimentar o ADSB.lol ou o airplanes.live, que são comunitários, é uma posição igualmente defensável. Não alimentar ninguém, também.

O que o feed realmente é

Tecnicamente, é decepcionantemente simples. Não existe cliente, agente ou daemon próprio para enviar os dados. O readsb, que já é o meu decodificador, ganha um destino a mais:

--net-connector feed.adsbexchange.com,30005,beast_reduce_out

É isso. Uma conexão TCP de saída para a porta 30005 deles, no formato Beast, com o sufixo reduce indicando taxa reduzida, que envia menos mensagens por segundo por aeronave, o suficiente para o agregador e mais leve para a rede.

Dá para confirmar que está funcionando olhando o processo e a conexão:

ss -tn state established | awk '$4 ~ /:30005$/'
journalctl -u readsb | grep -i "connection established"

No meu caso apareceu BeastReduce TCP output: Connection established: feed.adsbexchange.com, e uma conexão viva com o IP deles. Dado enviado é dado enviado, sem cerimônia.

O pacote de estatísticas é outra coisa

Aqui mora uma confusão que a documentação deles não ajuda a desfazer: alimentar e aparecer nas estatísticas são coisas separadas.

O feed acima já entrega os dados. Mas o receptor não aparece em lugar nenhum, não tem página, não tem número, não sabe quantas aeronaves contribuiu. Para isso existe um segundo pacote, o adsbexchange-stats, que gera um identificador único para o meu feeder, o UUID, e envia periodicamente estatísticas de recepção.

Com ele instalado, você ganha três endereços:

A página do feeder, em adsbexchange.com/api/feeders/?feed=<uuid>, com as estatísticas. O adsbexchange.com/myip/, que confirma se o IP de onde estou acessando está mesmo alimentando a rede, útil para bater o olho. E o account.adsbexchange.com, onde vincula o feeder a uma conta, que é o que dá acesso aos benefícios de quem contribui.

Os mapas, para quem só quer olhar, são o globe.adsbexchange.com, que é a interface principal, e o adsbexchange.com/data/ para acesso aos dados.

O myip é a forma mais rápida de saber se está tudo certo. No meu caso, Feed Ok e Stats Ok:

Página myip do ADSBExchange mostrando Feeder Status com ADS-B conectado, estatísticas ok e MLAT sem feed, com IP e Feed UID borrados

Repare no terceiro campo: MLAT, sem feed. Isso não é um defeito:

MLAT é multilateração, a técnica de cruzar o instante de chegada do mesmo sinal em receptores diferentes para calcular a posição de aeronaves que não transmitem a própria, como as que só respondem a radar secundário. Fazer parte disso exige um cliente separado, o mlat-client, que sincroniza relógio com os servidores deles. O --net-connector do readsb não faz isso: ele envia as mensagens que eu decodifico, e só.

Ou seja, alimentar ADS-B e alimentar MLAT são coisas distintas e dá para fazer as duas.

Vinculando o UUID a uma conta, o receptor aparece na área pessoal, e é ali que ficam o leaderboard, os alertas de queda do dispositivo e o acesso premium sem anúncios que a rede dá a quem contribui:

Página de receptores da conta ADSBExchange, com o formulário de vincular receptor e um dispositivo ativo listado, com identificadores borrados

E a página de estatísticas mostra o histórico do que o receptor entregou, uma amostra por minuto, mais a lista de aeronaves do instante, com posição, altitude e força de sinal. Estas 218 amostras são as primeiras horas do meu feeder no ar:

Página ADSBx Anywhere Stats com gráfico de contagem de aeronaves ao longo de 218 amostras e a tabela de voos captados com posição, altitude e RSSI

O vale no meio do gráfico é madrugada. Ele é, na prática, o tráfego de Guarulhos desenhado por hora do dia.

Um aviso importante sobre esse UUID: ele não é público. O script envia ele como cabeçalho HTTP para autenticar os envios, então quem tiver o meu UUID consegue tanto ver as suas estatísticas quanto enviar dados falsos no meu nome. Trate como credencial, não como número de série. Se vazar, apague o /usr/local/share/adsbexchange/adsbx-uuid e reinstale o pacote, que ele gera outro.

O identificador que aparece publicamente na página do feeder é outro, mais curto, e esse sim pode ser mostrado.

Dois problemas corrigidos no meu instalador

O site do ADSBExchange manda rodar isto para instalar as estatísticas:

curl -L -o /tmp/axstats.sh https://adsbexchange.com/stats.sh
sudo bash /tmp/axstats.sh

Isso não funciona no Arch.

O stats.sh tem onze linhas e é só um bootstrap: instala o git com apt-get e clona o repositório de verdade. O instalador real está lá dentro, e na linha 10 ele chama adduser, que é um utilitário do Debian e não existe no Arch. Diferente de outros scripts do ecossistema, aqui não há fallback para useradd. Como o script roda sob set -e, ele morre exatamente ali.

O resultado é um diretório vazio em /usr/local/share/adsbexchange-stats, nenhum arquivo copiado, nenhum serviço criado, nenhum UUID gerado. E a mensagem no site continua dizendo que você não tem o pacote configurado, por mais vezes que você repita o comando.

O segundo problema aparece depois de resolver o primeiro, e é mais sutil. O serviço instala, sobe, o systemctl mostra active e enabled, tudo verde. E ele não faz absolutamente nada:

No valid data source directory found, do you have the adsbexchange feed scripts installed?
Tried each of: [/run/adsbexchange-feed]

O script deles procura o JSON em /run/adsbexchange-feed, que é o diretório criado pelo pacote de feed do ADSBExchange. Quem alimenta direto pelo readsb, como nós, tem o JSON em /run/readsb. Ele nunca encontra, e repete o erro a cada 20 segundos para sempre.

A saída existe, e é deles mesmos: colocar USE_OLD_PATH=1 no /etc/default/adsbexchange-stats faz o script procurar em /run/readsb primeiro. Só que o instalador deles só escreve esse arquivo quando detecta uma imagem de Raspberry Pi, procurando por um /boot/adsb-config.txt. Numa máquina comum, nunca.

E tem um terceiro, puramente cosmético, que só registro pela curiosidade: no json-status deles a palavra printf está partida ao meio por uma quebra de linha, terminando uma linha com { pr e começando a seguinte com intf("1"). O perl reclama, o teste seguinte falha por variável vazia, e o efeito prático é o script não usar o cache de DNS próprio. Inofensivo, mas está lá, em produção, para todo mundo.

Isso tudo virou código

Como esta série já tinha rendido um instalador para o stack inteiro, achei natural resolver esses atritos ali também. O ADSBExchange agora tem um subcomando próprio:

easy1090 feed                       # lista as redes e o estado de cada uma
easy1090 feed adsbexchange          # habilita, com o pacote de estatísticas
easy1090 feed adsbexchange --disable

Ele cria o usuário de sistema com useradd antes de chamar o instalador deles, o que faz a verificação interna passar e o ramo do adduser ser pulado; resolve as dependências que o script só sabe buscar com apt ou yum; escreve o USE_OLD_PATH=1; reinicia o serviço; e imprime o UUID com as três URLs no final. O script de terceiro roda intacto, sem patch, exatamente como fiz com o tar1090 na parte 5.

O compartilhamento continua sendo opt-in, com pergunta explícita e resposta padrão negativa. Escrever “não compartilhar” como padrão foi decisão de projeto, não descuido: enviar a sua posição para servidores de terceiros tem que ser um ato deliberado.

O instalador é assunto do próximo artigo, que conta a construção dele por inteiro, incluindo a sequência de bugs que só apareciam na segunda execução.

A parte incômoda do MLAT: a sua geolocalização

O readsb tem uma opção:

--json-location-accuracy 2

Zero não publica, 1 publica aproximada, 2 publica exata. Ela decide o que vai no receiver.json local, o arquivo que o tar1090 lê para saber onde centralizar o mapa. Conferindo no meu servidor:

cat /run/readsb/receiver.json
# { "refresh": 1000, "history": 1, "lat": -23.583944, "lon": -46.554710, ... }

Coordenada exata, com precisão de metros. Mas isso está no MEU servidor, não no deles.

O que sai pelo --net-connector é o protocolo Beast, que carrega mensagens de aeronaves, não a posição de quem recebeu. E o pacote de estatísticas também não envia: procurei por latitude e longitude no json-status deles e não há nenhuma referência. Bate com o que a minha própria conta mostra, com as coordenadas do receptor em branco.

Quem precisa da sua posição exata é o MLAT, e por um motivo legítimo: sem saber onde cada receptor está, não dá para calcular posição por diferença de tempo de chegada. É justamente o serviço que, no meu caso, aparece como sem feed.

Então o desenho real da exposição é este. O ADSBExchange sabe o meu endereço IP, porque abre uma conexão TCP com eles, e sabe quais aeronaves recebi dados, o que já dá uma noção grosseira de onde você está. Mas não sabe a minha coordenada, a menos que adicione o MLAT. E quem realmente vê a posição com precisão de metros é qualquer pessoa capaz de abrir o meu mapa local, porque ela está no receiver.json.

Isso inverte a recomendação óbvia. Se o tar1090 estiver acessível fora da rede local, altere para 1 protege você de quem abre o mapa, e não muda nada do lado do agregador. E se um dia resolver alimentar MLAT também, aí sim estará entregando a coordenada exata.

E se eu quiser alimentar o airplanes.live também?

Pode, inclusive para ambos. As duas redes são conexões TCP independentes saindo do mesmo readsb, então alimentar uma não atrapalha a outra. A documentação do airplanes.live diz isso com todas as letras, que os scripts deles não interferem em clientes de feed já presentes.

O airplanes.live nasceu justamente no contexto do capítulo anterior deste artigo: é uma rede comunitária, sem filtro, criada depois da venda do ADSBExchange, por gente que não quis apostar no novo dono. Se o argumento de não filtrar é o que te move, alimentar as duas é a posição mais coerente possível, porque distribui o mesmo dado entre um agregador comercial e um comunitário.

O jeito oficial

O site deles manda rodar isto:

curl -L -o /tmp/feed.sh https://raw.githubusercontent.com/airplanes-live/feed/main/install.sh
sudo bash /tmp/feed.sh

Fui ler antes de rodar, como fiz com o do ADSBExchange, e encontrei o mesmo bug, escrito de forma independente por outra equipe. No update.sh deles, sob set -e, na linha 164:

adduser --system --home "$IPATH" --no-create-home --quiet "$UNAME" || adduser --system --home-dir "$IPATH" --no-create-home "$UNAME"

O comentário logo acima diz que a segunda forma é “para fedora / centos”. Só que as duas são adduser, que simplesmente não existe no Arch. Sem fallback para useradd, o script morre ali.

E tem um segundo buraco no mesmo arquivo: a instalação de dependências é uma cadeia if apt / elif yum / elif dnf / fi, sem else. Numa distro fora dessas três, nada é instalado, em silêncio, e o script segue até tropeçar em algo que precisava de socat ou de um ambiente virtual de Python.

Dois projetos diferentes, escritos por pessoas diferentes, com exatamente o mesmo ponto cego: presumir que todo receptor ADS-B é um Raspberry Pi rodando Debian. O que é uma presunção razoável em termos de mercado, e péssima em termos de código.

O jeito curto

Para ADS-B, você não precisa de pacote nenhum. Basta um destino a mais no readsb, exatamente como no ADSBExchange:

--net-connector feed.airplanes.live,30004,beast_reduce_plus_out,feed.airplanes.live,64004

Não inventei esse formato: é a mesma string que o instalador deles escreve, incluindo o endpoint de contingência na 64004. A diferença é que aqui ela vai direto no readsb que você já tem, sem um segundo decodificador intermediário e sem script de root nenhum.

Com as duas redes ligadas, o NET_OPTIONS fica assim:

--net-connector feed.adsbexchange.com,30005,beast_reduce_out
--net-connector feed.airplanes.live,30004,beast_reduce_plus_out,feed.airplanes.live,64004

E para conferir que as duas conexões subiram:

ss -tn state established | grep -E ':30004|:30005'

O status do feed fica em airplanes.live/myfeed/, e o mapa da rede em globe.airplanes.live.

Poucos minutos depois, o feed aparece reconhecido no myfeed deles:

Página myfeed do airplanes.live mostrando Beast Connection Detected e MLAT Connection Detected com selo de sucesso, e as métricas do feed BEAST-0, com o IP borrado

Vale a mesma ressalva do MLAT: o instalador oficial deles também configura multilateração, que exige um cliente separado e, aí sim, a sua coordenada exata. Quem quiser só ADS-B, como eu, resolve com a linha acima.

E aqui vale registrar um detalhe curioso. A página marca MLAT Connection Detected com selo de sucesso, mas essa máquina não alimenta MLAT. Conferi de três formas: o instalador oficial nunca rodou nela, não existe serviço airplanes-mlat, e não há conexão estabelecida nas portas 31090 nem 64004.

A prova definitiva estava logo abaixo, na mesma página. O myfeed também expõe um JSON com os dados brutos do feeder, e nele consta:

"mlat_clients": []

Lista vazia. Ou seja, o próprio site sabe que não há cliente MLAT nenhum, e ainda assim o indicador acima aparece verde. É bug de exibição deles, e o dado estruturado embaixo desmente o selo colorido em cima. Fica a dica de sempre olhar o JSON quando o painel bonito disser algo que a sua máquina não confirma.

O resto dos números confere com o que o meu receptor faz: 1,32 kbit/s de tráfego, 2,275 posições por segundo e latência de 266 ms até o servidor deles, na Alemanha.

E o resultado final, o meu dado no mapa deles, com 50 aeronaves na tela das quase 15 mil que a rede inteira via naquele momento:

Mapa do airplanes.live sobre São Paulo, com dezenas de aeronaves, o painel de detalhes de um Airbus A320neo da LATAM e a tabela lateral com callsign, altitude e RSSI

O easy1090 agora conhece as duas:

easy1090 feed                    # lista as redes e o estado de cada uma
easy1090 feed airplaneslive      # habilita
easy1090 feed airplaneslive --disable

Onde eu fico

Contribuo com o ADSBExchange, sabendo que ele foi vendido em 2023 e que isso pode mudar o que a rede é. A tese de não filtrar aeronaves segue de pé, e enquanto seguir, contribuir com ela é coerente com o que eu defendo aqui. E fica o registro de que a alternativa comunitária existe e é legítima. Se a promessa mudar, o ADSB.lol e o airplanes.live estão a um --net-connector de distância (e provavelmente serão meus proximos testes), o que é a beleza de todo esse stack ser aberto: quem decide para onde os seus dados vão é você, não o fabricante do dongle nem o site que você abre para ver o avião passar.


Share this post on:

Previous Post
easy1090: instalador completo
Next Post
Seu navegador está seguro? Um guia dos testes de vazamento e fingerprint