A empresa onde trabalho depende 100% da internet para seu funcionamento, devido a isso procurei escolher uma boa opção de link de internet levando em consideração o melhor preço X a melhor qualidade de serviço.
É claro que as melhores opções são sempre os links dedicados, no entanto esses serviços são caros se comparados aos serviços DSL disponíveis.
Depois de um bom tempo usando o Velox da Telemar, fui apresentado a MundiVox que até que tem um serviço de boa qualidade, só deixando a desejar no primeiro mês de uso.
Apesar da boa qualidade do serviço, inevitavelmente acontecem algumas pequenas interrupções que as vezes não passam de 1 minuto, mas as vezes pode passar dos 10. Sendo assim decidi contratar mais um link de internet de outro fornecedor para efeitos de backup.
Uma vez que o outro link já estava instalado e funcionando surgiu o desafio: Como inserir o novo link na rede de modo a garantir que a internet não pare nunca (failover
), e como combinar os 2 links para aumentar a velocidade de acesso a internet (load balancing
)?
Inicialmente pensei que a solução fosse utilizar o recurso de “bonding” do kernel, no entanto após ler bem a pouca documentação disponível descobri que o bonding só funciona com links que façam parte de uma mesma rede (lógica).
Pesquisando mais a fundo descobri que a solução está nas tabelas de roteamento do sistema, na verdade na parte Avançada de Roteamento do sistema (o conhecido Linux Advanced Routing and Traffic Control)
A idéia básica do sistema é de criar tabelas de roteamento separadas para cada link e utilizar um gateway multipath ( com rotas alternativas ) como rota padrão. Para isso é necessário utilizar o pacote iproute2, disponível em http://linux-net.osdl.org/index.php/Iproute2
O pacote iproute2 fornece ferramentas avançadas para a configuração de interfaces, endereços, rotas e filtros e pode substituir as ferramentas padrões já conhecidas: ifconfig, route, arp.
No kernel, a opção IP: advanced router
provê o suporte necessário para o recurso.
A configuração é simples e basicamente se dá em configurar o kernel e adicionar algumas tabelas de roteamento.
Configuração do kernel
# cd /usr/src/linux
# make menuconfig
Networking —>
Networking options —>
[*] IP: advanced router
[*] IP: policy routing
[*] IP: use netfilter MARK value as routing key
[*] IP: equal cost multipath
[ ] IP: equal cost multipath with caching support (EXPERIMENTAL)
O suporte a equal cost multipath with caching
ainda é experimental e não costuma funcionar corretamente, é altamente recomendado desativar essa opção.
Nos meus testes ao habilitar o recurso, as tabelas de roteamento demoravam muito mais tempo para serem trocadas (quando falhas) além de que não conseguia pingar endereços externos por ambas interfaces (embora o tráfego funcionasse).
Compile e instale o novo kernel
# make
# cp arch/i386/boot/bzImage /boot/kernel-2.6.17-gentoo-r4
Instalação do iproute2
O iproute2 está disponível na árvore do portage e portanto basta um emerge
para instalar o pacote.
# emerge iproute2
Usuários de outras distribuições podem usar os devidos sistemas de pacotes para instalar o iproute2 ou instalar a partir dos fontes.
# cd /usr/src/
# wget http://developer.osdl.org/dev/iproute2/download/iproute2-2.6.18-061002.tar.gz
# tar xzf iproute2-2.6.18-061002.tar.gz
# cd iproute2
# ./configure
# make
# make install
Configurando
Supondo que temos 2 links A e B (com IPs estáticos) de provedores distintos e gostaríamos de compartilha-los para nossa rede local na interface de rede C, podemos considerar o seguinte esquema:
INTERNET INTERNET
| |
ISP1 ISP2
10.0.0.1 172.16.0.1
eth1 eth2/
10.0.0.5 172.16.0.7
NAT ROUTER (eth0)
|192.168.0.1
----+-----------------+---
No esquema acima temos a seguinte configuração:
IP0 = 192.168.0.1
NET0 = 192.168.0.0
BCAST0 = 192.168.0.255
IP1 = 10.0.0.5
GW1 = 10.0.0.1
NET1 = 10.0.0.0
BCAST1 = 10.0.0.255
IP2 = 172.16.0.1
GW2 = 172.16.0.7
NET2 = 172.16.0.0
BCAST2 = 172.16.0.255
Onde IP(0,1,2) é o endereço IP da interface, GW(1,2) é o gateway, NET(0,1,2) é o endereço da rede e BCAST(0,1,2) é o endereço de broadcast.
Interfaces de rede
Devemos configurar as interfaces normalmente porém não deve ser adicionada uma rota default.
# ip link set eth0 up
# ip addr add IP0/24 broadcast BCAST0 dev eth0
# ip link set eth1 up
# ip addr add IP1/24 broadcast BCAST1 dev eth1
# ip link set eth2 up
# ip addr add IP2/24 broadcast BCAST2 dev eth2
(o número 24 após a barra representa o endereço de rede)
Tabelas de roteamento
Com o suporte a advanced router
e policy routing
no kernel, é possível criar tabelas de roteamento separadas para cada link.
O arquivo /etc/iproute2/rt_tables define as tabelas de roteamento disponiveis no sistema. Nele são definidas 4 tabelas padrões do sistema que são: local, main, default e unspec.
A tabela local
armazena as rotas para as redes configuradas no sistema e é manipulada diretamente pelo kernel. As rotas padrões (configuradas pelo usuário) são inseridas na tabela main
.
É possível ainda adicionar novas tabelas ao arquivo embora isso não seja extremamente necessário a menos que as tabelas sejam sempre chamadas através do nome ao invés do seu número.
# vi /etc/iproute2/rt_tables
#
# reserved values
#
255 local
254 main
253 default
0 unspec
#
# local
#
20 isp1
30 isp2
100 netgw
Certifique-se de que não existe nenhuma rota padrão configurada e defina a prioridade da tabela main para um número menor que os das interfaces de rede.
# ip route show table main|grep default
# ip route del default
# ip rule add prio 10 table main
Para que a configuração funcione corretamente é preciso se certificar de que os pacotes voltem sempre pela interface pela qual entraram. Dessa forma é necessário criar uma tabela separada para cada link especificando sua rota padrão. Também é necessário definir uma regra para cada tabela.
# ip rule add prio 20 table isp1
# ip route add route from NET1/24 default via GW1 dev eth1
# ip route append prohibit default table isp1 metric 1
# ip rule add prio 30 table isp2
# ip route add route from NET2/24 default via GW2 dev eth2
# ip route append prohibit default table isp2 metric 1
A linha com o parâmetro prohibit
funciona de forma similar ao REJECT
do itptables
. Quando um cliente está transferindo dados por uma certa interface e ela deixa de funcionar, uma mensagem ICMP
é enviada ao cliente de modo a fazer com que o mesmo termine a conexão e comece uma nova usando uma rota funcional.
Também é necessário criar uma tabela para o gateway multipath.
# ip rule add prio 100 table netgw
# ip route add default nexthop via GW1/24 dev eth1 nexthop via GW2/24 dev eth2
Pode-se utilizar o parâmetro weight
de modo a especificar o peso (preferência) da rota
NAT
O compartilhamento para a rede interna pode ser feito através do iptables mascarando a saída nas interfaces eth0 e eth1.
# iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
# iptables -t nat -A POSTROUTING -o eth2 -j MASQUERADE
# echo 1 > /proc/sys/net/ipv4/ip_forward
Testando
Nesse momento já deve ser possível acessar um endereço externo por ambas as rotas.
# ping google.com -c 1 -I eth1
PING google.com (64.233.167.99) from 10.0.0.5 eth1: 56(84) bytes of data.
64 bytes from 64.233.167.99: icmp_seq=1 ttl=243 time=191 ms
# ping google.com -c 1 -I eth2
PING google.com (64.233.167.99) from 172.16.0.1 eth2: 56(84) bytes of data.
64 bytes from 64.233.187.99: icmp_seq=1 ttl=243 time=115 ms
Vale lembrar que como o kernel guarda um cache das rotas utilizadas é provável que quando um dos links falhar demore alguns mimnutos até que a rota indisponível seja deletada e uma nova seja criada utilizando o link funcional.
É possível definir através do arquivo /proc/sys/net/ipv4/route/gc_timeout
o tempo máximo que o kernel espera até tentar uma nova rota quando perceber que a atual está morta.
Também é possível forçar a “limpeza” do cache de roteamento com o seguinte comando:
# ip route flush cache dev ethX
(onde X é o número da interface)
Dessa forma é possível criar um script que tente pingar os gateways (ou outro endereço público) e caso não haja resposta apagar o cache da tabela de roteamento.
Melhorando a detecção de rotas mortas
O kernel linux armazena as rotas de rede utilizadas em um tabela de cache que é consultada a cada acesso. Quando uma rota não é encontrada utiliza-se os gateways disponíveis para traçar uma nova rota e automaticamente ela é armazenada.
Quando tentamos acessar um endereço por uma certa rota e esta falha, não necessariamente temos problemas com o gateway ou com a interface, as vezes o problema pode ser com o endereço de destino. Dependendo do motivo da falha, certas ações são tomadas pelo sistema.
Após um certo tempo, o gateway será considerado como morto e marcado como indisponível (”unreachable”). Se o problema for com a interface o link será desativado e as rotas serão limpadas (flushed) do cache forçando a utilização de uma nova rota.
Caso o problema seja outro (um gateway “morto” no caminho por exemplo) não será possível detectar a falha e as tentativas de conexão continuarão tentando essa rota e falhan do. Após mais um certo tempo o cache de rotas irá expirar e só então uma nova rota (funcional) será tentatada.
Da mesma forma, caso a falha tenha sido da interface e/ou cabeamento, não será possível re-inserir as rotas automaticamente quando o problema se resolver automaticamente, necessitando assim de uma intervenção do administrador para ativação das mesmas.
O Julian Anastasov já há alguns anos disponibiliza alguns patchs para melhor esse recurso no kernel, tornando a detecção de gateway/rotas mortas mais inteligente. Os patches podem ser encontrados em http://www.ssi.bg/~ja/
Com os patches aplicados é possível por exemplo usar “prot static” para todas as rotas. Dessa forma, mesmo que uma interface se torne indisponível o kernel não irá remover as rotas e uma vez que a interface seja disponibilizada novamente tudo estará funcionando perfeitamente (ideal para esquemas de failover).
# cd /usr/src/
# wget http://www.ssi.bg/~ja/routes-2.6.17-12.diff
# patch -p1 < routes-2.6.17-12.diff
# cd linux
# make && make modules install
# cp arch/i386/boot/bzImage /boot/kernel-2.6.17-gentoo-r4
Configure o gerenciador de boot e reinicialize com o novo kernel
Atualizado: Gráficos Comparativos
O balanceamento de uso dos links pode ser comprovado nos gráficos abaixo. O primeiro gráfico representa as 2 semanas anteriores a instala~ao do segundo link. Os demais representam 2 semanas de uso com os 2 links.
Automatizando a configuração no Gentoo
No gentoo é possível definir funções no arquivo de configurações de redes. Tais funções são executadas antes/após carregar uma interface e antes/após descarregar uma interface. Dessa forma é possível automatizar o processo de criar as rotas e regras.
A função postup
que implemento abaixo não é muito inteligente, porém dá uma idéia básica de como automatizar o processo de configuração das redes.
A cada interface que for configurada as ações de adicionar/remover regra/rota são executadas.
# vi /etc/conf.d/net
config_eth0=( “192.168.0.1 netmask 255.255.255.0″ )
config_eth1=( “10.0.0.5 netmask 255.255.255.0″ )
config_eth2=( “172.16.0.1 netmask 255.255.255.0″ )
append_eth0=( “default via 10.0.0.1 src 10.0.0.5 proto static table isp1″
“prohibit default table isp1 metric 1 proto static”)
append_eth1=( “default via 172.16.0.7 src 172.16.0.1 proto static table isp2″
“prohibit default table isp2 metric 1 proto static”)
rule_eth0=( “from 10.0.0.0/24 priority 20 table isp1″ )
rule_eth1=( “from 172.16.0.0/24 priority 30 table isp2″ )
multipath_route=( “table netgw proto static nexthop via 10.0.0.1
dev eth1 nexthop via 172.16.0.7 dev eth2″ )
multipath_rule=( “prio 100 table netgw”
“prio 10 table main” )
postup() {
local x=”append_${IFVAR}[@]”
local -a appends=( “${!x}” )
if [[ -n ${appends} ]] ; then
einfo “Appending route”
eindent
for x in “${appends[@]}”; do
ip route del ${x} 1>&2 2> /dev/null
ebegin “${x}”
ip route append ${x}
eend $?
done
eoutdent
#Flush the cache
ip route flush cache dev “${IFACE}”
fi
local x=”rule_${IFVAR}[@]”
local -a rules=( “${!x}” )
if [[ -n ${rules} ]]; then
einfo “Adding IP policy routing rules”
eindent
for x in “${rules[@]}”; do
ip rule del ${x} 1>&2 2> /dev/null
ebegin “${x}”
ip rule add ${x}
eend $?
done
eoutdent
fi
if [[ -n ${multipath_route} ]]; then
for x in “${multipath_route[@]}”; do
einfo “Adding multipath route”
eindent
ip route del default 1>&2 2> /dev/null
ip route del ${x} 1>&2 2> /dev/null
ebegin “${x}”
ip route add default ${x}
eend $?
eoutdent
done
fi
if [[ -n ${multipath_rule} ]]; then
for x in “${multipath_rule[@]}”; do
einfo “Adding multipath rule”
eindent
ip rule del ${x} 1>&2 2> /dev/null
ebegin “${x}”
ip rule add ${x}
eend $?
eoutdent
done
fi
}