Introdução ao BIRD
O BIRD (BIRD Internet Routing Daemon), é um daemon de roteamento dinâmico projetado para sistemas do tipo UNIX, como Linux, FreeBSD, NetBSD e OpenBSD. Ele permite que esses sistemas operacionais atuem como roteadores dinâmicos, tomando decisões de roteamento com base em protocolos como BGP, OSPF, RIP, Babel e outros. O BIRD funciona como um daemon, ou seja, um processo em segundo plano, responsável por calcular e manter tabelas de roteamento, que o sistema operacional usa para encaminhar pacotes de rede de forma eficiente e adaptativa.
O projeto BIRD começou como um trabalho estudantil na Faculdade de Matemática e Física da Universidade Charles, em Praga, na República Tcheca. Posteriormente, passou a ser mantido e desenvolvido pelos laboratórios do CZ.NIC, a organização responsável pelo registro do domínio .cz
. A equipe atual inclui desenvolvedores como Ondřej Filip, Martin Mareš, Ondřej Zajíček e Maria Matějka, que contribuíram com módulos como OSPF, BGP, BFD, MPLS, filtros e multithreading.
O BIRD foi criado para suprir limitações de outros daemons existentes na época, como o routed
(limitado ao RIP), o GateD (não livre), Zebra e MRTD, que eram difíceis de configurar e com funcionalidades restritas. Ele se destaca pela arquitetura modular, suporte a múltiplas tabelas de roteamento, compatibilidade com IPv4 e IPv6, linguagem poderosa de filtragem de rotas e pela possibilidade de reconfiguração suave, ou seja, aplicar mudanças na configuração sem reiniciar o daemon nem afetar os protocolos de roteamento ativos.
Se você pretende trabalhar com o BIRD, há alguns pontos fundamentais que você precisa entender bem para operá-lo com segurança e eficiência.
Arquitetura
A arquitetura do BIRD gira em torno de três blocos principais: tabelas de roteamento, protocolos e canais.
As tabelas guardam as rotas. Elas não fazem forwarding, só armazenam e decidem qual é a melhor rota para cada prefixo. A decisão é baseada em preferências e filtros. Por padrão existem duas: master4
para IPv4 e master6
para IPv6, mas você pode criar quantas quiser.
Os protocolos são módulos como BGP, OSPF, RIP, Babel, kernel (sincroniza com o sistema), device (lê interfaces), static (rotas estáticas), entre outros. Cada instância de protocolo é isolada, com sua própria lógica.
Os canais conectam cada protocolo a uma tabela. É no canal que você aplica filtros de importação (entrada) e exportação (saída). Isso permite controlar o que entra e o que sai em cada sessão.
Há também suporte para múltiplas RIBs, filtros potentes, e um cliente de linha de comando (birdc
) para monitoramento. O BIRD pode rodar com privilégios reduzidos e suporta graceful restart, mantendo rotas vivas no kernel durante reinicializações.
Versões mais novas têm suporte a multithreading e MPLS, o que torna o BIRD viável para ambientes exigentes como route servers em IXPs.
Tabelas de Roteamento
As tabelas de roteamento no BIRD são o núcleo onde todas as rotas são armazenadas, comparadas e selecionadas. Elas não encaminham pacotes, apenas mantêm a lógica de qual rota deve ser usada. O encaminhamento real é feito pelo kernel, se houver um protocolo kernel
ativo para sincronizar os dados.
Por padrão, o BIRD cria duas tabelas: master4
para rotas IPv4 e master6
para IPv6. Você pode criar tabelas adicionais com o comando table nome { }
, o que é útil em cenários com múltiplas RIBs ou roteadores virtuais (como route servers em IXPs).
Cada tabela é associada a um tipo de rede: IPv4, IPv6, VPN (com RD), ROA, Flowspec ou MPLS. Para cada prefixo (ou chave primária, dependendo do tipo de rota), o BIRD mantém todas as rotas recebidas e seleciona apenas uma como melhor. As demais continuam armazenadas como subótimas, o que permite fallback rápido se a rota principal for retirada.
Essas tabelas podem ser configuradas para manter a lista de rotas ordenada (sorted
) ou usar uma estrutura em trie para facilitar buscas como superprefixos e subprefixos, algo necessário, por exemplo, para validação RPKI ou Flowspec.
Você não manipula as tabelas diretamente. Elas são alimentadas por protocolos conectados a elas por canais. O comportamento da tabela depende da qualidade das rotas que ela recebe e dos filtros definidos nesses canais.
Se quiser exportar rotas para o kernel (e com isso ativar o roteamento de fato no sistema), você precisa configurar um protocolo kernel
vinculado a essa tabela. Sem isso, a tabela funciona apenas como banco de dados lógico de rotas.
Protocols
O BIRD é dividido em módulos, chamados de protocols, que são instâncias específicas de protocolos de roteamento como BGP, OSPF, RIP, Babel, ou até mesmo o protocolo kernel, que serve para sincronizar rotas entre o BIRD e o sistema operacional.
Cada protocolo no BIRD opera de forma independente, mas se conecta a uma ou mais routing tables através de channels, e essas tabelas são o núcleo onde as decisões de roteamento são tomadas. Por padrão, ele usa as tabelas master4
para IPv4 e master6
para IPv6, mas você pode definir quantas quiser para usos mais sofisticados, como roteadores virtuais, interconexões de VRFs ou serviços de route server com múltiplas RIBs.
Uma das principais vantagens do BIRD é a sua linguagem de configuração, que não só define os protocolos e interfaces, mas também permite filtros altamente customizáveis. Os filtros são escritos com uma linguagem específica do BIRD, que te dá controle sobre o que importar ou exportar, com base em atributos como prefixo, next-hop, comunidades BGP, atributos AS_PATH, entre outros. Você pode reescrever rotas, bloquear anúncios e aplicar políticas detalhadas com uma clareza que outros daemons muitas vezes não oferecem.
Outro ponto essencial é a maneira como ele lida com mudanças. O BIRD pode ser reconfigurado sem ser reiniciado, você edita o arquivo de configuração, sinaliza o processo com um SIGHUP
, e ele aplica as mudanças. Ele só reinicia protocolos que foram afetados pela mudança. Isso permite operar em ambientes críticos sem perder sessões BGP, por exemplo.
Há também um cliente interativo chamado birdc
que você usa para interagir com o daemon em tempo real. Com ele, é possível monitorar sessões de BGP, ver o conteúdo das tabelas de roteamento, aplicar comandos administrativos como shutdown de protocolos, triggers de reconfiguração, entre outros.
Além disso, o BIRD pode operar com privilégio reduzido, o que é importante para segurança. Embora precise de root para abrir sockets brutos e manipular as tabelas de roteamento, ele pode mudar para um usuário não privilegiado assim que iniciar, mantendo apenas as permissões mínimas necessárias (via CAP_NET_*
no Linux).
Por fim, ele é extremamente modular e performático. As versões atuais (especialmente o BIRD 2 e 3) já têm suporte a multithreading e podem utilizar múltiplos núcleos, o que é essencial para ambientes com alto número de sessões BGP, como em route servers de IXPs.
Ele também suporta graceful restart, permitindo que rotas sejam mantidas no kernel durante uma reinicialização planejada do daemon, evitando perda de conectividade.
Protocol Direct
O protocolo direct
serve para importar automaticamente rotas diretamente conectadas, ou seja, as rotas que aparecem nas interfaces de rede ativas do sistema. Ele detecta prefixos configurados nas interfaces e os injeta na tabela de roteamento do BIRD, sem precisar que outro protocolo anuncie essas rotas.
Ele é útil especialmente quando você quer que essas rotas estejam visíveis no BIRD, para redistribuí-las via BGP ou OSPF, ou simplesmente para aplicar filtros internos. Sem o direct
, o BIRD ignora as rotas diretamente conectadas, ele não vê o que está nas interfaces a menos que um protocolo como o kernel ou o direct as traga para dentro da tabela.
A configuração básica é simples:
protocol direct {
ipv4;
ipv6;
}
Se você tiver muitas interfaces e não quer que todas as rotas entrem na tabela, pode especificar quais interfaces devem ser consideradas com a opção interface
:
protocol direct {
ipv4 {
import all;
};
interface "eth*";
}
Aqui, ele importa todas as rotas IPv4 diretamente conectadas em interfaces cujo nome começa com "eth".
Importante, o direct
só importa as rotas, ele não interfere no encaminhamento, nem altera o estado das interfaces. E ele só traz as rotas que estão realmente atribuídas nas interfaces ativas do sistema operacional.
Se você está construindo uma topologia que depende de rotas locais visíveis para outros protocolos, o direct
é essencial. Sem ele, o BIRD simplesmente não vai "enxergar" os prefixos locais.
Protocol Device
O protocolo device
é o responsável por monitorar o estado das interfaces de rede do sistema operacional. Ele não importa ou exporta rotas. O que ele faz é garantir que o BIRD saiba quando uma interface sobe ou desce, o que é crucial para o funcionamento de protocolos dinâmicos como OSPF, RIP, Babel, BGP, que dependem diretamente da presença das interfaces para ativar vizinhanças.
Sem o device
, o BIRD não consegue detectar mudanças nas interfaces, o que significa que protocolos que usam detecção automática de interfaces simplesmente não funcionam corretamente. Por isso, é comum ver esse protocolo em toda configuração básica, geralmente assim:
protocol device {
scan time 10;
}
Ele é simples, não precisa de filtros nem configurações complexas. Ele deve estar ativado sempre que você usa qualquer protocolo baseado em interface, como OSPF, RIP, BGP, MPLS.
Internamente, o device
escuta eventos de estado da camada de rede (como UP, DOWN, IP atribuído, remoção de IP) e os repassa para os protocolos interessados. Ele é passivo e silencioso, mas sem ele, o BIRD literalmente "fica cego" para a topologia local.
Se você está rodando apenas BGP e já especificou manualmente os peers, o device
pode até ser dispensável. Mas em setups mais dinâmicos, ele é obrigatório.
Protocol Static
O protocolo static
no BIRD serve para definir rotas estáticas diretamente no bird.conf
. Essas rotas são inseridas na tabela de roteamento do BIRD sem depender de qualquer protocolo de roteamento dinâmico ou do estado das interfaces. É útil quando você quer forçar o roteamento de certos prefixos por caminhos fixos.
A configuração mais simples é:
protocol static {
ipv4;
route 192.0.2.0/24 via 10.0.0.1;
}
Isso insere na tabela uma rota para a rede 192.0.2.0/24
, usando 10.0.0.1
como próximo salto. O BIRD vai manter essa rota na tabela mesmo que não haja confirmação de que o next-hop esteja alcançável, a não ser que o kernel a recuse ou a interface caia (dependendo de como o kernel lida com isso).
Você pode aplicar filtros a essas rotas, usá-las como base para redistribuição em BGP ou OSPF, e pode definir múltiplas rotas dentro do bloco protocol static
.
Também é possível definir rotas com mais controle, como:
route 198.51.100.0/24 reject;
route 203.0.113.0/24 blackhole;
Essas instruções criam rotas que não encaminham pacotes (reject
manda ICMP unreachable, blackhole
descarta o pacote silenciosamente).
Se quiser que o BIRD remova as rotas quando for encerrado, não coloque a opção persist;
. Por padrão, o BIRD remove as rotas estáticas quando é finalizado, a menos que você diga o contrário com:
protocol static {
ipv4;
persist;
route 10.1.1.0/24 via 192.168.1.1;
}
O protocolo static
usa import
mas não tem export
. Isso porque ele é um produtor de rotas, ele insere rotas na tabela, mas não consome nenhuma. Então não faz sentido exportar para ele, só importar.
Para que serve o import?
Se você quiser aplicar um filtro às rotas definidas dentro do static
, você deve usar o import filter
dentro do canal, o import
serve apenas para aplicar filtro. Esse filtro age sobre as rotas estáticas antes de serem aceitas pela tabela. Se o filtro rejeitar uma rota, ela nem entra.
Um exemplo com IPv6:
protocol static {
ipv6 {
import filter only_prefix_aceito;
};
route 2001:db8:1::/48 via 2001:db8::1;
route 2001:db8:2::/48 via "eth0";
}
Aqui, o protocolo static
está tratando rotas IPv6. A primeira rota vai para o next-hop 2001:db8::1
, e a segunda envia pacotes para essa rede pela interface eth0
diretamente. O filtro only_prefix_aceito
determina se essas rotas entram na tabela.
No protocolo static
, não é obrigatório declarar explicitamente o bloco ipv4
ou ipv6
para que as rotas sejam processadas. O BIRD identifica automaticamente o tipo da rota com base no prefixo declarado. Se você colocar rotas IPv6 diretamente, mesmo fora de um bloco ipv6
, ele cria internamente um canal IPv6 para lidar com elas.
protocol static {
import all;
route 192.0.2.0/24 via 10.0.0.1;
route 2001:db8::/32 via 2001:db8::1;
}
Nesse caso, o BIRD trata cada rota conforme o tipo (a primeira como IPv4, a segunda como IPv6) sem que você precise declarar ipv4 {}
ou ipv6 {}
explicitamente. Ele cria os canais automaticamente e aplica o import all
a ambos.
A única situação onde você precisa declarar os blocos é se quiser aplicar filtros diferentes ou configurações separadas para cada tipo de rota. Caso contrário, ele trata tudo de forma implícita e funcional.
Protocol Kernel, Kernel4 e Kernel6
O protocolo kernel
no BIRD é usado para sincronizar rotas entre o BIRD e a tabela de roteamento do sistema operacional (a FIB do kernel). Ele funciona nos dois sentidos,pode exportar rotas da tabela do BIRD para o kernel (para que o sistema possa encaminhar pacotes com base nessas rotas) e pode importar rotas do kernel para dentro do BIRD, e assim o BIRD poderá redistribuir essas rotas em outros protocolos.
No BIRD 3, você não pode misturar canais de tipos diferentes (como ipv4 {}
e ipv6 {}
) dentro da mesma instância do protocolo kernel
. Cada instância de protocol kernel
só pode ter um canal (ou IPv4, ou IPv6). Portanto, você precisa declarar duas instâncias distintas do protocolo kernel
, uma para cada família de endereços:
Ou, separando em instâncias distintas:
protocol kernel kernel4 {
ipv4 {
export all;
};
}
protocol kernel kernel6 {
ipv6 {
export all;
};
}
Os nomes kernel4
e kernel6
são apenas rótulos arbitrários (você pode chamá-los como quiser), mas é obrigatório que cada um contenha apenas um tipo de canal. Isso é uma exigência da arquitetura do BIRD 3.x, diferente do que ocorria em versões anteriores onde era possível declarar ambos os canais numa única instância.
Caso esteja trabalhando apenas com IPv4 ou apenas com IPv6, pode usar apenas protocol kernel
, como no exemplo abaixo, mas apenas se for um único protocolo de transporte:
# Apenas IPv6:
protocol kernel {
ipv6 {
export all;
};
}
# Apenas IPv4:
protocol kernel {
ipv4 {
export all;
};
}
Cada instância kernel
pode ser configurada com opções como:
persist;
: mantém as rotas no kernel mesmo após o BIRD ser encerrado.learn;
: permite importar rotas adicionadas ao kernel por outros daemons ou pelo administrador se o S.O permitir identificar a autoria. Por padrão, rotas "proto kernel" não são importadas, uselearn all
para importar também essas.scan time N;
: define o intervalo de varredura da tabela do kernel (em segundos).import
: Importa as rotas do Kernel para dentro da tabela do BIRD. Útil quando você quer que o BIRD veja rotas aprendidas por DHCP, adicionadas manualmente com ip route add, ou trazidas por outro processo.export
: Exporta as rotas do BIRD para dentro do Kernel. Isso permite que o sistema possa usá-las para encaminhar pacotes.
Protocol OSPF, BGP, MPLS
São os protocolos usados para trabalhar OSPF, BGP, MPLS entre outros.
Canais
Os canais no BIRD são o elo entre os protocolos (como BGP, OSPF, kernel) e as tabelas de roteamento. Quando você configura um protocolo, ele se conecta a uma tabela por meio de um canal. É nesse canal que você define filtros de importação e exportação, ou seja, decide que rotas o protocolo pode enviar para a tabela e quais pode receber dela.
Você pode pensar no canal como um "fio de comunicação" entre o protocolo e a tabela. No caso do BGP, por exemplo, ele pode ter múltiplos canais, um para IPv4, outro para IPv6, ou até para VPNv4, cada um com sua própria configuração de filtro e opções.
Dentro de um protocolo, você define o canal assim:
protocol bgp meu_bgp {
ipv4 {
import filter meu_filtro_import;
export filter meu_filtro_export;
};
...
}
Esse bloco ipv4 { ... }
é o canal. O que está ali só se aplica às rotas IPv4 tratadas por essa instância de BGP. Se você quiser adicionar um canal para IPv6, precisa declarar ipv6 { ... }
também. E se quiser associar o canal a uma tabela diferente, usa a opção table nome_da_tabela
.
Cada canal mantém suas estatísticas, como número de rotas importadas e exportadas, o que facilita o diagnóstico em ambientes com múltiplos peers e rotas. E o uso de múltiplos canais dentro do mesmo protocolo é muito comum em setups com mais de uma RIB ou múltiplos tipos de rota sendo trocadas na mesma sessão (como em VPNs ou route servers).
Por fim, canais também são fundamentais para recursos como MPLS, onde o protocolo precisa de um canal especial para se conectar a uma tabela de rotas MPLS. Sem canal, o protocolo não troca rotas, ele existe, mas não se comunica com a lógica central do roteamento no BIRD.
Importando e Exportando Rotas
Dentro de um canal no BIRD, os blocos import
e export
controlam o fluxo de rotas entre o protocolo e a tabela de roteamento. Eles são fundamentais para aplicar políticas de roteamento.
O import
define o que o protocolo pode inserir na tabela. Ou seja, quando um BGP, OSPF ou RIP recebe uma rota de um peer ou vizinho, ele só vai repassar essa rota para a tabela se passar no filtro de importação.
O export
é o contrário, define o que a tabela pode entregar ao protocolo. Quando a tabela tem uma rota selecionada, o protocolo só vai anunciar essa rota ao vizinho se ela passar no filtro de exportação.
Um exemplo prático:
protocol bgp upstream {
ipv4 {
import filter filtro_entrada;
export filter filtro_saida;
table master4;
};
local as 65000;
neighbor 203.0.113.1 as 65001;
}
Aqui, filtro_entrada
decide o que o BIRD vai aceitar da sessão BGP com o peer 203.0.113.1. Já o filtro_saida
decide o que o BIRD vai anunciar de volta para esse peer. Se qualquer uma dessas linhas for none
, nenhuma rota entra ou sai. Se for all
, entra ou sai tudo sem filtragem. Se for um filtro nomeado, você pode escrever regras específicas com base em prefixo, AS_PATH, comunidades, e atributos.
Esses filtros são executados em tempo real à medida que as rotas são processadas. E é aqui que está o poder do BIRD, você pode construir lógicas detalhadas como: "aceite só rotas de /24
vindas de AS específico", ou "não exporte rotas com a comunidade 65535:666".
Filtros
Os Filtros no BIRD são o mecanismo central para controle de rotas. Eles funcionam como scripts aplicados no ponto onde rotas entram ou saem de canais, ou seja, entre um protocolo (como BGP, OSPF, static, kernel) e a tabela de roteamento. Com eles, você decide se uma rota será aceita, rejeitada, modificada ou marcada com atributos específicos.
Você pode aplicar filtros nos blocos import
e export
de cada canal. O BIRD executa esse filtro toda vez que uma rota passa por ali. O filtro pode usar atributos como prefixo (net
), ASN (bgp_path
), comunidades (bgp_community
), next-hop (gw
), tipo de rota (source
), preferências (preference
), e muito mais.
A estrutura de um filtro é parecida com uma função em C ou linguagem de script. Exemplo:
filter filtro_cliente {
if net ~ [ 192.0.2.0/24, 198.51.100.0/24 ] then accept;
reject;
}
Esse filtro aceita só dois prefixos, e rejeita todo o resto.
Você também pode modificar atributos da rota antes de aceitar:
filter marca_pref {
bgp_local_pref = 200;
accept;
}
E ainda usar expressões mais elaboradas:
filter analisa_aspath {
if bgp_path.first = 64512 then accept;
reject;
}
Além de filtros nomeados (que você declara com filter nome { ... }
e reutiliza onde quiser), também é possível declarar filtros inline diretamente no import
ou export
, usando a palavra-chave where
.
import where net ~ [ 10.0.0.0/8 ];
Aqui, estamos dizendo: "importe apenas rotas cujo prefixo (net
) pertença à rede 10.0.0.0/8
". Não há necessidade de criar um filtro nomeado separado, essa lógica é aplicada diretamente naquele canal, e só ali.
Filtros também podem ser combinados com listas de comunidades, listas de prefixos, testes lógicos encadeados e até chamadas para funções auxiliares que você define no início do bird.conf
.
Eles são fundamentais em ambientes de route server, redes com múltiplas políticas de trânsito, redes com clientes com regras próprias de anúncio, ou qualquer cenário onde você precisa selecionar com cuidado o que entra ou sai.
No fundo, o filtro é onde você transforma o BIRD num roteador de verdade, com decisões baseadas em política, não só em conectividade.
Colchetes e til
No BIRD, os colchetes []
são usados para declarar listas, e o operador ~
serve para testar se um determinado valor está presente dentro dessas listas. Essa combinação é muito comum em filtros e é essencial para aplicar políticas com base em grupos de prefixos ou comunidades. Quando você escreve algo como:
if net ~ [ 192.0.2.0/24, 198.51.100.0/24 ] then accept;
Está dizendo: "se o prefixo da rota (net
) estiver na lista de prefixos entre colchetes, aceite a rota". O ~
aqui é o equivalente a "pertence a", e os colchetes definem o conjunto contra o qual o valor será testado.
Você pode aplicar esse mesmo conceito com máscaras de prefixo:
if net ~ [ 10.0.0.0/8{16,24} ] then accept;
Nesse caso, o filtro só aceita prefixos dentro da rede 10.0.0.0/8
, mas com máscaras que variem entre /16
e /24
. Esse tipo de sintaxe só funciona dentro de listas entre colchetes, e é uma forma poderosa de restringir o que entra com base no tamanho dos blocos IP.
Outro uso muito comum é com comunidades BGP:
if (65000,100) ~ bgp_community then accept;
Aqui, a lógica se inverte, você testa se uma comunidade específica está presente na lista de comunidades da rota. O lado esquerdo é um valor, o lado direito é um atributo da rota que contém uma lista, e o ~
continua significando "está contido em".
Esse recurso deixa os filtros do BIRD extremamente expressivos e permite implementar políticas complexas com clareza e precisão.
Funções
No BIRD, funções são trechos de lógica reutilizável que você define no início do arquivo de configuração (antes de qualquer protocolo) para facilitar a construção de filtros mais legíveis e organizados. Elas funcionam como mini scripts que retornam um valor, geralmente true
, false
, ou um valor atribuído a algum atributo da rota.
Elas não têm estado, não recebem parâmetros nomeados como em linguagens tradicionais, e são sempre avaliadas dentro do contexto de um filtro. A sintaxe é simples:
function rotas_de_cliente() {
return net ~ [ 192.0.2.0/24, 198.51.100.0/24 ];
}
Essa função retorna true
se o prefixo estiver na lista especificada. Você pode usar ela dentro de qualquer filtro:
filter entrada {
if rotas_de_cliente() then accept;
reject;
}
Você também pode usar funções para testar comunidades, AS_PATH, next-hop, ou até construir lógicas encadeadas:
function rota_via_ixp() {
return bgp_next_hop = 198.51.100.1;
}
Ou ainda:
function rota_invalida() {
return bgp_path.len > 5 || (65000,666) ~ bgp_community;
}
Funções deixam os filtros mais limpos, facilitam manutenção e evitam repetir blocos de lógica em vários pontos. Elas não aceitam parâmetros, mas você pode encapsular qualquer expressão booleana ou cálculo que se baseie no contexto da rota sendo avaliada. É um recurso essencial quando você começa a aplicar políticas mais sofisticadas.