Balanceamento de links com ip dinamico
Colaboração: Altemir Braz Dantas Junior
Data de Publicação: 10 de outubro de 2011
Sempre fiz balanceamento de links com ip fixo usando iproute2 + iptables,
até que um cliente que tinha 3 links de internet com ip dinamico,
ai pensei "puts vai zoar as rotas do balanceamento toda hora que trocar o gateway,
vou ter que criar varios scripts para ficar monitorando e trocar a rota, vamos googlear".
O google é meu pastor e nada me faltará. rsrsrsr
Achei o que queria e vou apresentar-lo meu salvador dhclient-script, disponível a partir da versão 3 do dhclient.
Verifiquei que através dele eu poderia pegar informações importantes como as do exemplo abaixo
OBS: Nao mencionarei regras de iptables (INPUT, OUTPUT, FORWARD , MASQUERADE e etc) aqui, somente a criacao das rotas
reason='RENEW'
interface='eth1'
medium=''
alias_ip_address=''
new_ip_address='189.xxx.xxx.xxx'
new_subnet_mask='255.255.240.0'
new_domain_name=''
new_domain_search=''
new_domain_name_servers='8.8.8.8 4.2.2.2'
new_routers='189.xxx.xxx.1'
new_static_routes=''
old_ip_address='189.yyy.yyy.yyy'
old_subnet_mask='255.255.240.0'
old_domain_name='spo.virtua.com.br'
old_domain_search=''
old_domain_name_servers='8.8.8.8 4.2.2.2'
old_routers='189.yyy.yyy.1'
old_static_routes=''
E o melhor ele me retorna como variaveis para eu usar no meu shell script na hora de cada ação
O dhclient-script usa o Bourne Shell.
Para voces entenderem melhor leiam o man.
Ambiente
- eth0 - rede interna
- eth1 - link1 100MG
- eth2 - link2 20MG
- eth3 - link3 4MG
- link1 usei a marca 1 e peso 6
- link2 usei a marca 2 e peso 3
- link3 usei a marca 3 e peso 1
Como uso debian vou mostrar como fiz nesta distribuição.
Instalando pacotes necessários
apt-get install dhclient3 ipcalc iproute2
Primeiramente vou criar as tabelas no iproute2
echo "201 link1" >> /etc/iproute2/rt_tables
echo "202 link2" >> /etc/iproute2/rt_tables
echo "203 link3" >> /etc/iproute2/rt_tables
Agora cria minhas functions
mkdir /etc/scripts
vi /etc/scripts/function.sh
Contendo o seguinte
# recebe a interface da rede e retorna o nome da tabela cadastrada
pega_tabela(){
DEV=$1
case $DEV in
eth1)
TABELA="link1"
return 1
break
;;
eth2)
TABELA="link2"
return 1
break
;;
eth3)
TABELA="link3"
return 1
break
;;
esac
}
# recebe a interface da rede e retorna o numero da marcacao para poder usar rotas pela marcacao feita pelo iptables
pega_marca(){
DEV=$1
case $DEV in
eth1)
MARK="1"
return 1
break
;;
eth2)
MARK="2"
return 1
break
;;
eth3)
MARK="3"
return 1
break
;;
esac
}
# recebe a interface da rede e retorna o peso para fazer o balanceamento
pega_peso(){
DEV=$1
case $DEV in
eth1)
PESO="6"
return 1
break
;;
eth2)
PESO="3"
return 1
break
;;
eth3)
PESO="1"
return 1
break
;;
esac
}
# recebe a tabela e retorna o ip do gateway da mesma
pega_gateway(){
TABELA=$1
GATEWAY=$(ip route show table $TABELA | grep default | awk -F' ' '{ print $3 }')
}
# recebe a tabela e deleta todas as rotas da mesma
del_rotas(){
/sbin/ip route flush table $1
}
# recebe a tabela e deleta todas as regras da mesma
del_regras(){
ip rule show | grep $1 | cut -d : -f2 | while read RULES; do ip rule del $RULES ; done
}
# recebe a tabela,ip,marca e cria regras de roteamento para aquela tabela
add_regras(){
TABELA=$1
IP=$2
MARK=$3
ip rule add fwmark $MARK table $TABELA
ip rule add from $IP table $TABELA
}
# recebe a tabela,ip,interface,ip da rede e o ip do gateway para criar as rotas para aquela tabela
add_rotas(){
TABELA=$1
IP=$2
DEV=$3
REDE=$4
GW=$5
/sbin/ip route add $REDE dev $DEV src $IP table $TABELA
/sbin/ip route add default via $GW dev $DEV table $TABELA
}
# deleta todas as rotas padrao
del_rotas_padrao(){
# deletando as rotas default
# deleta a rota criada pelo dhcp
/sbin/ip route del default
# deleta a rota do balanceamento
/sbin/ip route del default
}
# recebe interface,ip do gateway e peso para criar as regras de balanceamento
add_rota_balanceamento(){
DEVNEW=$1
GWNEW=$2
PESONEW=$3
case $DEVNEW in
eth1)
pega_tabela eth2
pega_peso eth2
pega_gateway $TABELA
TABELA2=$TABELA
PESO2=$PESO
GW2=$GATEWAY
DEV2=eth2
pega_tabela eth3
pega_peso eth3
pega_gateway $TABELA
TABELA3=$TABELA
PESO3=$PESO
GW3=$GATEWAY
DEV3=eth3
;;
eth2)
pega_tabela eth1
pega_peso eth1
pega_gateway $TABELA
TABELA2=$TABELA
PESO2=$PESO
GW2=$GATEWAY
DEV2=eth1
pega_tabela eth3
pega_peso eth3
pega_gateway $TABELA
TABELA3=$TABELA
PESO3=$PESO
GW3=$GATEWAY
DEV3=eth3
;;
eth3)
pega_tabela eth1
pega_peso eth1
pega_gateway $TABELA
TABELA2=$TABELA
PESO2=$PESO
GW2=$GATEWAY
DEV2=eth1
pega_tabela eth2
pega_peso eth2
pega_gateway $TABELA
TABELA3=$TABELA
PESO3=$PESO
GW3=$GATEWAY
DEV3=eth2
;;
esac
if [ "$GWNEW" == "0" ]; then
if [ "$GW2" != "" -a $GW3 != "" ]; then
/sbin/ip route add default nexthop via $GW2 dev $DEV2 weight $PESO2 nexthop via $GW3 dev $DEV3 weight $PESO3
else
if [ "$GW2" != "" ]; then
/sbin/ip route add default via $GW2
else
/sbin/ip route add default via $GW3
fi
fi
else
if [ "$GW2" != "" -a $GW3 != "" ]; then
/sbin/ip route add default nexthop via $GWNEW dev $DEVNEW weight $PESONEW nexthop via $GW2 dev $DEV2 weight $PESO2 nexthop via $GW3 dev $DEV3 weight $PESO3
else
if [ "$GW2" != "" ]; then
/sbin/ip route add default nexthop via $GWNEW dev $DEVNEW weight $PESONEW nexthop via $GW2 dev $DEV2 weight $PESO2
else
if [ "$GW3" != "" ]; then
/sbin/ip route add default nexthop via $GWNEW dev $DEVNEW weight $PESONEW nexthop via $GW3 dev $DEV3 weight $PESO3
else
/sbin/ip route add default via $GWNEW
fi
fi
fi
fi
return 1;
}
Criado o arquivo das functions agora vou criar os scripts para o dhclient-script
Dentro do diretorio /etc/dhcp/
existem dois diretorios o dhclient-enter-hooks.d/
e o dhclient-exit-hooks.d/
e o arquivo dhclient.conf
No arquivo dhclient.conf
procure a linha comentada
#prepend domain-name-servers
Descomente e coloque os seguintes DNS publicos
prepend domain-name-servers 8.8.8.8 4.2.2.2;
Assim toda vez que o servidor dhcp nos atribuir seus servidores DNS ele vai adicionar esses 2 antes do dele.
Em relação aos diretorios antes dele, fazer alteracoes em nosso /etc/resolve.conf
o dhclient
script vai checar se há scripts primeiramente dentro de dhclient-enter-hooks.d/
usando o "." (ponto).
Quem usa Bourne Shell (bash) sabe que é como um include
no diretorio. Depois de executar ele faz a mesma operacao no diretorio dhclient-exit-hooks.d/
Para mais informações
man dhclient-script
Criei então o seguinte script dentro do diretorio dhclient-enter-hooks.d/
:
vi /etc/dhcp/dhclient-enter-hooks.d/rotas_avancadas
Contendo
# da um include na minha function
. /etc/scripts/function.sh
# ok = 0 -> link fora - deleta rotas para esse link
# ok = 1 -> link ok e novo ip - altera rotas desse link
# ok = 2 -> link ok e o ip nao mudou portanto nao faca nada
# setei ok = 2 para nao fazer nada
ok=2
if [ "$reason" == "REBOOT" -o "$reason" == "RENEW" -o "$reason" == "BOUND" -o "$reason" == "REBIND" ]; then
# Se o novo ip for igual a nada setar ok = 0
if [ "$new_ip_address" == "" ]; then
ok=0
fi
# Se o novo ip tem o inicio igual 192 quer dizer que o dhcp me deu ip porem esta sem internet entao seta ok = 0
inicioip=$(echo $new_ip_address | cut -d "." -f 1)
if [ "$inicioip" == "192" ]; then
ok=0
fi
# se o ok continua igua a 2 é porque ele passou dos itens acima
if [ "$ok" == "2" ];then
# se o ip diferente do antigo seto o ok=1
if [ "$new_ip_address" != "$old_ip_address" ];then
ok=1;
fi
fi
fi
# Se aontecer uma das acoes abaixo é que o servidor dhcp falhou entao seto o ok=0
if [ "$reason" == "FAIL" -o "$reason" == "TIMEOUT" -o "$reason" == "EXPIRE" ]; then
ok=0;
fi
Depois disso criei o seguinte script
vi /etc/dhcp/dhclient-exit-hooks.d/rotas_avancadas
Contendo
# ja tenho os meus ok definidos no script anterior e minha functions incluidas tambem ai faco minhas acoes
if [ "$ok" == "1" ]; then
# pego minhas variaveis
pega_tabela $interface
pega_marca $interface
pega_peso $interface
# calcula o ip da rede
ipcalcule=$(echo "$new_ip_address/$new_subnet_mask")
my_new_network=$(ipcalc -n $ipcalcule | grep Network | cut -b 12-32)
# deleta regra antiga e cria regras novas
del_regras $TABELA
add_regras $TABELA $new_ip_address $MARK
#deleta rotas antigas e cria novas
del_rotas $TABELA
add_rotas $TABELA $new_ip_address $interface $my_new_network $new_routers
del_rotas_padrao
add_rota_balanceamento $interface $new_routers $PESO
fi
if [ "$ok" == "0" ]; then
pega_tabela $interface
del_rotas $TABELA
del_regras $TABELA
del_rotas_padrao
add_rota_balanceamento $interface 0 0
fi
Agora é só executar
dhclient -v eth1
dhclient -v eth2
dhclient -v eth3
Olhar como ficou as rotas
ip route show
ip route show table link1
ip route show table link2
ip route show table link3
Para ver como ficaram as regras:
ip rule show
Para testar com o ip route get para ver qual a rota que ele usaria para sair para um determinado destino:
ip route get 8.8.8.8
ip route get 4.2.2.2
ip route get 174.120.154.93
E fazer seus testes
Altemir Braz Dantas Junior (jocajuni) http://acessa.me/@joca, http://acessa.me - crie seu atendimento online gratuito
Linux e Mercado
Colaboração: Fátima Conti
Visto em http://visualoop.tumblr.com/post/9430080645/linux-market-shares
Opinião dos Leitores
jmuramatsu
06 Dez 2011, 08:26
Olá Joca, outra coisa que percebi é que no script você marca o link1 com o fwmark 1 e o link2 com o fwmark 2, então no firewall eu teria que criar na tabela mangle a marca das saídas, por exemplo, sites https assim:
#iptables -t mangle -A PREROUTING -p tcp --dport 443 -d 0.0.0.0/0.0.0.0 -j MARK --set-mark 1
Pra todos IPs internos sairem pelo link1, certo? Fora isso tem outro jeito, já que tentei assim e não consegui?
Valeu
jmuramatsu
05 Dez 2011, 14:26
Olá Joca, blz?
O balanceamento está ok, funcionando normalmente.
Agora tenho um problema, veja se vc consegue me ajudar, como tem 2 links as vezes eu entro em algum site que tenha login (banco, etc) e depois de logar se eu clico em algum link ele volta pra tela pra fazer login novamente, acho que ele foi por um link e depois balanceou pra outro link, na tabela eu tentei colocar um IP interno pra acessar somente por um link com o comando "ip rule add from 10.10.10.101 lookup speedy" e "ip route flush cached" mas não funcionou, se eu testo pelo www.meuip.com.br ele retorna os 2 IPs diferentes (speedy e virtua), vc saberia me dizer como eu seto um IP ou site especifico pra sair somente por um IP (speedy ou virtua)?
Obrigado
Altemir Braz Dantas Junior
18 Out 2011, 14:10
Pode comentar sim pois ele estará pegando o dhclient pelo rc.local
#auto eth0
#iface eth0 inet dhcp
#auto eth1
#iface eth1 inet dhcp
[]s
Altemir Braz Dantas Junior - Jocajuni
jmuramatsu
18 Out 2011, 11:11
Altemir, muito obrigado, era isso mesmo que faltava, valeu mesmo. Agora está dividindo a conexão entre os links.
Outra coisa, o interfaces está assim:
auto eth0
iface eth0 inet dhcp
auto eth1
iface eth1 inet dhcp
Tá certo isso, né? Ou posso retirar a linha "iface ethx inet dhcp" pra ele pegar pelo "dhclient -v ethx"?
Muito obrigado pela ajuda cara.
[]'s
Altemir Braz Dantas Junior
17 Out 2011, 16:05
Isso realmente não tinha testado se no boot ele nao executa,
na teoria era para funcionar , entao vamos fazer o seguinte.
Quando vc executa na mao funciona ne?
Faca o seguinte teste coloque essas 2 linhas no seu /etc/rc.local antes da linha do exit 0;
dhclient -v eth0
dhclient -v eth1
exit 0
e reboota e veja se funciona
[]s
Altemir Braz Dantas Junior (Jocajuni)
jmuramatsu
17 Out 2011, 07:32
Olá Altemir, adicionei o echo mas não aparece nada no boot, pelo que vi ele não roda o /etc/dhcp/dhclient-enter-hooks.d/rotas_avancadas no boot, porque depois de logado dei o comanado # dhclient -v eth0 e ele apareceu o echo e depois adicionou a rota. Isso não era pra rodar no boot, na hora que ele pega o IP pelo DHCP? Olha o que aparece no boot:
Listening on LPF/eth0/00:0x:5x:65:x3:7x
Sending on LPF/eth0/00:0x:5x:65:x3:7x
Sending on Socket/fallback
DHCPDISCOVER on eth0 to 255.255.255.255 port 67 interval 5
Setting kernel variables ...done.
DHCPDISCOVER on eth0 to 255.255.255.255 port 67 interval 5
DHCPOFFER from 189.19.216.1
DHCPREQUEST on eth0 to 255.255.255.255 port 67
DHCPACK from 189.19.216.1
bound to 189.19.216.98 -- renewal in 36381 seconds.
Internet Systems Consortium DHCP Client 4.1.1-P1
Copyright 2004-2010 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/
Listening on LPF/eth1/00:0x:5x:65:x9:xx
Sending on LPF/eth1/00:0x:5x:65:x9:xx
Sending on Socket/fallback
DHCPDISCOVER on eth1 to 255.255.255.255 port 67 interval 4
DHCPDISCOVER on eth1 to 255.255.255.255 port 67 interval 7
DHCPOFFER from 189.47.144.1
DHCPREQUEST on eth1 to 255.255.255.255 port 67
DHCPACK from 189.47.144.1
bound to 189.47.144.99 -- renewal in 42891 seconds.
done.
[]'s
Altemir Braz Dantas Junior
14 Out 2011, 13:11
Realmete deve tger algo de errado.
faca o seguinte coloque nas functions os ecos dos comandos exemplo
add_rotas(){
TABELA=$1
IP=$2
DEV=$3
REDE=$4
GW=$5
/sbin/ip route add $REDE dev $DEV src $IP table $TABELA
/sbin/ip route add default via $GW dev $DEV table $TABELA
echo "/sbin/ip route add $REDE dev $DEV src $IP table $TABELA"
echo "/sbin/ip route add default via $GW dev $DEV table $TABELA"
}
veja se ele imprime o comando corretamente se imprimir corretamente copia e cole e veja se da erro ao executar
[]s
Joca
jmuramatsu
14 Out 2011, 07:33
Então Altemir, coloquei os pesos igual a 1 e continua igual, notei que alguns comandos não estão retornando o conteúdo das tabelas, veja:
# ip route show
189.19.216.0/24 dev eth0 proto kernel scope link src 189.19.216.98 #link virtua
189.47.144.0/24 dev eth1 proto kernel scope link src 189.47.144.99 #link speedy
10.10.10.0/24 dev eth2 proto kernel scope link src 10.10.10.254 #rede interna sendo o 10.10.10.254 o firewal/squid
default via 189.47.144.1 dev eth1 #placa speedy
default via 189.19.216.1 dev eth0 #placa virtua
# ip route show table virtua
Não retorna nada
# ip route show table speedy
Não retorna nada também
# ip rule show
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
Não deveria aparecer as tabelas virtua e speedy que eu criei no /etc/iproute2/rt_tables?
Estou achando que a minha /etc/scripts/function.sh tem algum pau, ela não deveria setar essas 2 tabelas com os IPs/Gateways?
OBS: meu /etc/rc.local só tem exit 0, é isso mesmo? Não tem que adicionar nada aqui?
Obrigado pela ajuda.
Altemir Braz Dantas Junior
13 Out 2011, 18:54
Fez Certinho.
Faça um teste coloca os dois pesos como 1
levante as regras novas
baixe 4 downloads simultaneos
Veja os resutados
[]s
Altemir Braz Dantas Junior (Jocajuni)
jmuramatsu
13 Out 2011, 14:30
Olá Altemir, muito bom o seu artigo.
Eu tenho 2 links (Virtua e Speedy) e fiz algumas alterações nos scripts, veja se está certo:
Ambiente:
eth0 - link1 10Mb
eth1 - link2 4Mb
eth2 - rede interna
link1 usei a marca 1 e peso 4
link2 usei a marca 2 e peso 1
Criei 2 novas entradas na /etc/iproute2/rt_tables:
201 virtua #equivalente ao link1
202 speedy #equivalente ao link2
Alterei a /etc/scripts/function.sh pra 2 placas (eth0-virtua e eth1-speedy), está certo ou fiz alguma besteira?
# recebe a interface da rede e retorna o nome da tabela cadastrada
pega_tabela(){
DEV=$1
case $DEV in
eth0)
TABELA="virtua"
return 1
break
;;
eth1)
TABELA="speedy"
return 1
break
;;
esac
}
# recebe a interface da rede e retorna o numero da marcacao para poder usar rotas pela marcacao feita pelo iptables
pega_marca(){
DEV=$1
case $DEV in
eth0)
MARK="1"
return 1
break
;;
eth1)
MARK="2"
return 1
break
;;
esac
}
# recebe a interface da rede e retorna o peso para fazer o balanceamento
pega_peso(){
DEV=$1
case $DEV in
eth0)
PESO="4"
return 1
break
;;
eth1)
PESO="1"
return 1
break
;;
esac
}
# recebe a tabela e retorna o ip do gateway da mesma
pega_gateway(){
TABELA=$1
GATEWAY=$(ip route show table $TABELA | grep default | awk -F' ' '{ print $3 }')
}
# recebe a tabela e deleta todas as rotas da mesma
del_rotas(){
/sbin/ip route flush table $1
}
# recebe a tabela e deleta todas as regras da mesma
del_regras(){
ip rule show | grep $1 | cut -d : -f2 | while read RULES; do ip rule del $RULES ; done
}
# recebe a tabela,ip,marca e cria regras de roteamento para aquela tabela
add_regras(){
TABELA=$1
IP=$2
MARK=$3
ip rule add fwmark $MARK table $TABELA
ip rule add from $IP table $TABELA
}
# recebe a tabela,ip,interface,ip da rede e o ip do gateway para criar as rotas para aquela tabela
add_rotas(){
TABELA=$1
IP=$2
DEV=$3
REDE=$4
GW=$5
/sbin/ip route add $REDE dev $DEV src $IP table $TABELA
/sbin/ip route add default via $GW dev $DEV table $TABELA
}
# deleta todas as rotas padrao
del_rotas_padrao(){
# deletando as rotas default
# deleta a rota criada pelo dhcp
/sbin/ip route del default
# deleta a rota do balanceamento
/sbin/ip route del default
}
# recebe interface,ip do gateway e peso para criar as regras de balanceamento
add_rota_balanceamento(){
DEVNEW=$1
GWNEW=$2
PESONEW=$3
case $DEVNEW in
eth0)
pega_tabela eth1
pega_peso eth1
pega_gateway $TABELA
TABELA2=$TABELA
PESO2=$PESO
GW2=$GATEWAY
DEV2=eth1
;;
eth1)
pega_tabela eth0
pega_peso eth0
pega_gateway $TABELA
TABELA2=$TABELA
PESO2=$PESO
GW2=$GATEWAY
DEV2=eth0
;;
esac
if [ "$GWNEW" == "0" ]; then
if [ "$GW2" != "" ]; then
/sbin/ip route add default nexthop via $GW2 dev $DEV2 weight $PESO2
else
/sbin/ip route add default via $GW2
fi
else
if [ "$GW2" != "" ]; then
/sbin/ip route add default nexthop via $GWNEW dev $DEVNEW weight $PESONEW nexthop via $GW2 dev $DEV2 weight $PESO2
else
if [ "$GW2" != "" ]; then
/sbin/ip route add default nexthop via $GWNEW dev $DEVNEW weight $PESONEW nexthop via $GW2 dev $DEV2 weight $PESO2
else
/sbin/ip route add default via $GWNEW
fi
fi
fi
return 1;
}
Bom, o resto ficou igual.
No Firewall eu adicionei as linhas:
# INICIO - load balance
# Variaveis para compartilhamento
### Multi WAN ###
ETH="eth+"
### Rede Lan ###
LAN="10.10.10.0/24" #rede interna
# Ativa o compartilhamento
iptables -t nat -A POSTROUTING -o $ETH -s $LAN -j MASQUERADE
# Ativa acessos que podem ir de uma interface mas que voltam por outra
# Fundamental estar desabilitado para funcionar o roteamento com 2 links.
echo 0 > /proc/sys/net/ipv4/conf/default/rp_filter
# Tempo maximo que o kernel espera ate tentar uma nova rota quando perceber que a atual esta morta.
echo 10 > /proc/sys/net/ipv4/route/gc_timeout
# FIM - load balance
Quando testo no Debian ele funciona, se desligo o cabo da eth0 (virtua) ele fica com a eth1 (speedy) funcionando, se desligo o cabo da eth1 ele funciona a eth0, até ai tudo bem.
Agora a dúvida, quando utilizo uma estação com Win XP e baixo algum arquivo pelo DownThemAll do Firefox (ou qualquer outro download) ele só funciona o eth0, não divide o tráfego, tipo 4 conexões pra eth0 e 1 pra eth1, o modem do Speedy (eth1) fica parado, só funciona quando desligo a eth0, é assim mesmo?
Valeu