Supongamos que viene el dueño de una empresa y les dice:
dueño- hola joven, mire usted, para el tipo de negocio que llevo necesito mucho ancho de bandaEsto les puede pasar en cualquier momento (el numero puede variar) así que estén atentos, yo por mi parte les dejo unos tips de como no morir en el intento.
geek- bueno... existe speedy 5Megas,con eso debería bastarle
dueño: bien, pude funcionar... me llevo 13.
geek: (plop?!)
-Indice
1-Objetivos
- Balancear los ADSL en una única conexión lógica (conntrack aware), pudiendo tener enlaces de distintas velocidades y proveedores, incluso conexiones no ADSL.
- Que el número de ADSL pueda ir de 2 a N.
- Monitorizar los ADSL para usar solo los que tengan servicio, y poder dar aviso o tomar otras medidas en caso de que haya enlaces caídos.
- Que la solución sea genérica y facilmente replicable en otros servidores.
- Que todo lo anterior sea automático 100%, es decir una vez hecho el setup que ya no requiera de intervención.
- Ver gráficos del uso parcial y total (queda para otro post)
- Controlar ancho de banda, para usuarios que están detrás (idem arriba :-)
2-Requisitos
- Linux >=2.6.12 (Usé Ubuntu Hardy Server LTS, grosso ya le dedicaré un post)
- Bash >=2
- Iptables
- Iproute2
3-Desarrollo
geek- hola, buen día, necesito 13 placas de red PCIPor ahora vamos a imaginar que encontramos un mother con 13 ports PCI así nos focalizamos en hacer funcionar el load balancing y una vez resuelto eso, retomaremos el tema de las 13 placas de red.
empleado(casa de computación)- bien aquí tiene
geek- bien, ahora necesito un mother para usarlas todas juntas
empleado- (plop?!)
3.1-Primer paso: configurar los routers/modems ADSL
Una vez contratados los ADSL (conviene usar distintos providers para minimizar los cortes), se presenta la primer decisión clave:- Levantar PPPOE con Linux (modem ADSL bridgeando)
- Levantar PPPOE con el propio modem (modem ADSL routeando)
Levantando los ADSL desde el router/modem, puedo usar una ip y un gw fijo en mi linux y eso hará que pueda usar el script con cualquier conexión que tenga ip fija y ya no importa si es ADSL o no.
Hoy por hoy es bastante común que el modem que nos entregan(siempre que sea ethernet) sea modem y router, y venga preconfigurado como router.
Dentro de los modems/routers que vi x ahí(todos low-cost) la mejor experiencia la tuve con los Zyxel, demostraron funcionar bastante bien, y tienen una interfaz via telnet bastante amena.
Así que entonces vamos a configurar cada modem en modo router, es decir con la interfaz WAN levantando PPP y la interfaz LAN apuntando hacia el linux.
Elegimos subredes /24 para que visualmente sea más intuitivo y en este caso elegí el rango de 10.0.0.0 porque la red LAN del Linux era en el rango 192.168.0.0/16 y separo así(visual y lógicamente) las dos redes.
Este es el esquema de lo que estamos buscando(*):
IP LAN modem/router ADSL IP "WAN" Linux 10.0.2.1 10.0.2.2 10.0.3.1 10.0.3.2 10.0.4.1 10.0.4.2 ... ... 10.0.14.1 10.0.14.2(*) empezamos desde 2, porque el 1 es un VLAN ID reservado, y me dio miedo usarlo :-S
3.2-Segundo paso: configurar el Linux
Para la configuración del linux necesitaba algo generico y sencillo de replicar,fundamentalmente para que el próximo cliente me amortice todo lo que no pude cobrar en este :-), y porque todo lo que hago trato de hacerlo así .La solución fue ordenar el caos en un solo bash script (o casi). El script se llama firewall.sh porque en los servidores que administro las reglas de firewall siempre las pongo en ese archivo. En este caso veremos reglas de firewall(iptables) comandos de tc y otras yerbas en ese mismo script.
Lo más simple para entender la solución es analizar el script paso a paso.
Primero tenemos las variables de configuración, esto es lo que hace al script genérico la idea es tocar solo aquí cada vez que lo movemos a otro escenario.
- #!/bin/bash
- ############ CONF GRAL ############
- adsl_ifaces=(eth0.2 eth0.3 ... eth0.14)
- adsl_ips=(10.0.2.2 10.0.3.2 ... 10.0.14.2)
- adsl_gws=(10.0.2.1 10.0.3.1 ... 10.0.14.1)
- adsl_weight=(1 1 ... 1)
- adsl_upload=(256 256 ... 256)
Si, en cambio, querés entender un poco como funciona (y si en algún momento hay problemas vas a querer saber),seguí leyendo.
Estas lineas hacen al funcionamiento del script en si, para que pueda llamarlo con parámetros especiales para debugear o para ver lo que va a hacer sin que haga efectivamente nada. Pueden saltearse sanamente estas lineas e ir directamente al próximo parrafo.
- ############ THE SCRIPT ############
- #PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
- test "$1" == "debug" && set -x
- test "$1" == "show" && iptables() { echo iptables "$@"; } && ip() { echo ip "$@"; } && tc() { echo tc "$@"; } && ifconfig() { echo ifconfig "$@"; }
- test "$1" == "loadvars" && return 0
- configurar la IP en cada interfaz
- configurar una tabla de ruteo exclusiva para esa
conexión, y una regla de ruteo.
Esto lo necesitamos para:- hacer salir por esa conexión lo que entra por esa conexión
- bindearnos a la interfaz y hacer pruebas de conectividad (ping -I, wget –bind-address)
- poner una queue para limitar el ancho de banda (upload) por cada interfaz (esto lo hacemos porque los modems de ADSL tienen buffers muy grandes y si no limitamos el buffer se llena y tenemos varios segundos de delay)
El formato es muy sencillo, les dejo un ejemplo:
11 adsl1Ahora nos toca hacer todo lo que enumeramos por cada conexión.
12 adsl2
13 adsl3
...
24 adsl14
- # por cada conexión
- for ((n=0;n<${#adsl_ifaces[@]};n++)); do
- # doy de alta la interfaz
- ifconfig ${adsl_ifaces[n]} ${adsl_ips[n]} netmask 255.255.255.0 up
- # borro lo viejo
- ip route flush table adsl$((n+1)) 2>/dev/null
- ip rule del from ${adsl_ips[n]} table adsl$((n+1)) 2>/dev/null
- tc qdisc del dev ${adsl_ifaces[n]} root 2>/dev/null
- # baja latencia y queue en los adsl , usamos tbf que rulea para esto
- tc qdisc add dev ${adsl_ifaces[n]} root tbf rate ${adsl_upload[n]}kbit latency 50ms burst 1540
- # armo la tabla de ruteo “adsl$n” copiando la tabla main y cambiando el default gateway
- while read line ;do
- test -z "${line##default*}" && continue
- test -z "${line##nexthop*}" && continue
- ip route add $line table adsl$((n+1))
- done < \
- <(/sbin/ip route ls table main)
- ip route add default table adsl$((n+1)) proto static via ${adsl_gws[n]} dev ${adsl_ifaces[n]}
- # creo la regla de ruteo para salir por esta talba si tenga esta source address
- ip rule add from ${adsl_ips[n]} table adsl$((n+1))
- # guardo para crear el balanceo
- multipath="$multipath nexthop via ${adsl_gws[n]} dev ${adsl_ifaces[n]} weight ${adsl_weight[n]}"
- done
- # ahora creo el default gw con multipath en la tabla main
- ip route del default 2>/dev/null
- ip route add default proto static $multipath
- # flush de cache de ruteo
- ip route flush cache
Dos potenetes razones
- porque necesitamos un balanceo connection-aware o un como también se le dice un router statefull. Es decir a medida que se generen nuevas conexiones (paginas web, chat de messenger, clientes p2p, sesiones ftp, etc) cada una de estas va a tomar un rumbo distinto, pero tenemos que asegurarnos de que cada conexión una vez establecida, siga saliendo siempre por el mismo enlace, de otra manera su ip de origen cambiaría y la conexión se perdería.
- porque quizá querramos NATear los paquetes salientes.
- ############ IPTABLES ############
- iptables -F
- iptables -X
- iptables -t nat -F
- iptables -t nat -X
- iptables -t mangle -F
- iptables -t mangle -X
To NAT or not to NAT, he ahí el dilema.
Hay que elegir entre natear dos veces (en el linux y en el router) o complicarnos un poco más la vida por el bien de nuestros usuarios y natear solo una vez en el modem, y con el linux simplemente routear.Para hacer esto en el linux solo necesitamos tener ip_forward activado, el famoso
echo 1 > /proc/sys/net/ipv4/ip_forwardo mas prolijo en /etc/sysctl.conf:
net.ipv4.ip_forward=1Y luego en cada adsl configurar la ruta hacia nuestra LAN. Si x ejemplo tenemos la LAN 192.168.0.0/16, hay que agregar una ruta estática en los adsl.
Por ejemplo en el modem con ip 10.0.2.1 agregamos(pseudocódigo):
add 192.168.0.0/16 gateway 10.0.2.2Y con eso basta, por lo que he podido comprobar los routers/modems natean todo lo que sale por su interfaz ppp así que esas ips 192.168.0.0/16 que el linux hace llegar crudas a los ADSL son NATeadas y luego el router sabe como devolver los paquete gracias a la ruta que le agregamos.
La otra opción era usar NAT en el Linux y no tener que tocar el router, habría que agregar esto al script(lo dejo comentado en el original):
- ############ NAT ############
- # - hago masquerade por cada interfaz/conexión
- for ((n=0;n<${#adsl_ifaces[@]};n++)); do
- iptables -t nat -A POSTROUTING -o ${adsl_ifaces[n]} -j MASQUERADE
- done
Firewall statefull (connection tracking)
Si simplemente balanceamos las conexiones agregando una ruta multipath y nada más, vamos a tener un problema.Las rutas tienen un cache de unos 5 minutos por default. Entonces cuando el cache de la ruta que usé para loguearme en MSN messenger expire, me va a tocar otra ruta nueva para llegar al servidor de MSN, es decir, voy a seguir mandando packetes de una conexión establecida desde una ip de origen que nunca estableció la conexión, ergo el MSN se me va a cortar, y así me va a pasar con muchos otros protocolos, como puede ser una sesión de ssh o de un home banking.
Primer intento...
El primer intento fue aumentar el cache de las tablas de ruteo, hacerlo de un día por ejemplo, pero tiene varias contrasUno es que realmente desaprovecha el balanceo de carga, porque por cada host de destino uso únicamente un enlace, y además, cada vez que se cae un enlace necesariamente debo flushear la tabla de ruteo, perdiendo todas las rutas cacheadas y genreando las no deseadas desconexiones.
Segundo intento... (la solución final)
Usando las listas del LUGMen recibí ayuda de Diego Woitasen, quien me hizo dar cuenta de que podíamos usar conntrack para mantener la correspondencia entre cada conexión y su respectivo enlace.Conntrack es un modulo de netfilter(iptables) que nos da la posibilidad de guardar(--save-mark) una marka particular para un paquete que pertenece a una conexión. Una vez markado ese paquete, luego me puedo parar en los paquetes entrantes y decirles que restauren su marka (--restore mark). Cada packete tendrá una marka que identifica al enlace al que pertenece y de esa manera puedo tomar la decisión de ruteo de por cual enlace salir.
De esta forma tenemos un firewall multipath y satefull. Obviamente hay excepciones(todo lo que conntrack no puede trackear) pero son mínimas.
Entonces agregamos las reglas de iptables para hacer conntrack
- ############ CONNTRACK ############
- # restauro la marka en PREROUTING antes de la decisión de ruteo.
- iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
- # CONNTRACK para el multipath
- # creo una tabla aparte
- iptables -t mangle -N my_connmark
- # y hago pasar los packetes que aún nunca fueron markados
- # (siempre serán paquetes que inician una conexión)
- iptables -t mangle -A FORWARD -m mark --mark 0 -j my_connmark
- # una vez procesado, borro la marka por si quiero usar las markas para otras cosas
- # como x ej QoS y control ancho de banda
- iptables -t mangle -A FORWARD -j MARK --set-mark 0x0
- # y ahora el contenido de la tabla aparte: my_conntrack
- # para la LAN no me hace falta conntrack ya que tengo una sola interfaz
- iptables -t mangle -A my_connmark -o eth1 -j RETURN
- # por cada conexión
- for((n=0;n<${#adsl_ifaces[@]};n++)); do
- #asocio una marka a cada interfaz
- iptables -t mangle -A my_connmark -o ${adsl_ifaces[n]} -j MARK --set-mark 0x$((n+1))
- iptables -t mangle -A my_connmark -i ${adsl_ifaces[n]} -j MARK --set-mark 0x$((n+1))
- done
- # la guardo para después poder hacer el –restore-mark en PREROUTING
- iptables -t mangle -A my_connmark -j CONNMARK --save-mark
- # por ultimo uso ip ru para hacer que el packete use la tabla de ruteo que le corresponde
- for ((n=0;n<${#adsl_ifaces[@]};n++)); do
- ip ru del fwmark 0x$((n+1)) table adsl$((n+1)) 2>/dev/null
- ip ru add fwmark 0x$((n+1)) table adsl$((n+1))
- done
3.3-El script completo(firewall.sh)
Descargar el script completo: firewall.sh4-Verificando cada conexión y actualizando el default gateway
Vamos a hacer ahora un segundo script, que debería correr cada unos 5 minutos por ejemplo, el cual va a testear la conectividad de cada uno de esos enlaces y actualizara la ruta multipath para que figuren solo los enlaces que están respondiendo.Como hicimos con el script anterior lo analizamos por partes.Antes que nada cargo la info de cada adsl usando las mismas variables de firewall.sh así cualquier cambio que realice allí, este script actuará acorde.
Para que eso funcione tenemos que colocar la ruta completa de donde tengamos nuestro archivo firewall.sh
- #!/bin/bash
- source /path/to/firewall.sh loadvars
Existen en internet lo que se conoce como root nameservers, que son la última autoridad cuando se hace una consulta de DNS. Estos son servidores redundantes y de alta disponibilidad, con una conectividad maravillosa, mantenidos por la ICANN. Como hacen funcionar a internet, alguno puede caer, pero no todos a la vez.
Estos "root nameservers" son 13, y se usan letras de la A a la M para para su identificación. Lo que hice fue testear cuales de esos servidores respondían ping (icmp echo), y quedaron 10 servidores.
- #A to M ROOT DNS world servers ip address
- #I choose those that seems to accept icmp-echo-requests
- root_nameservers="B C D E F I J K L M"
- multipath_total=0
- for((n=0;n>${#adsl_ifaces[@]};n++)); do
- pong=0
- for letter in $root_nameservers; do
- if(ping -c1 -W2 $letter.root-servers.net -I ${adsl_ips[n]} &>/dev/null);then
- pong=1
- # el doble espacio antes de dev _does_matter_
- multipath="$multipath nexthop via ${adsl_gws[n]} dev ${adsl_ifaces[n]} weight ${adsl_weight[n]}"
- let multipath_total+=1
- break
- fi
- done
- #if no one answers
- if [[ $pong == 0 ]];then
- #ejemplo telnet y reset, para un zyxel (opciones 24, 4 y 21)
- # user=user;pass=pass;
- #echo -e "$user\n$pass\n$24\n4\n21\n" | telnet ${adsl_gws[n]} &
- # ejemplo hacer sonar el beep
- #echo -e "\a"
- # ejemplo mail
- # echo “ip:${adsl_ips[n} iface:${adsl_ifaces[n]}” | mail -s “se cayo la conexión ${adsl_ips[n]}” someone@foo.bar
- # logueo para futuro análisis
- echo `date`" la conexion con ${adsl_gws[n]} esta down" >> /var/log/multipath_watchdog.log
- fi
- done
- #si todos están caídos dejo todo como estaba
- test -z "${multipath}" && exit 1
- # cargo en $route el multipath actual
- while read line ;do
- test -z "${line##default*}" && begin=1
- test "$begin" == 1 && route="$route ${line}"
- done < \
- <(/sbin/ip route ls)
- # armo el multipath de los que están up para poder comparar
- # tengo que preguntar xq si hay solo un enlace up, la sintaxis cambia
- if [[ $multipath_total > 1 ]];then
- # el doble espacio antes de proto _does_matter_
- route_multipath=" default proto static${multipath}"
- else
- route_multipath=${multipath#nexthop }
- route_multipath=${route_multipath% weight*}
- route_multipath=" default ${route_multipath/ dev/ dev} proto static"
- fi
- #printf "%q\n" "${route}"
- #printf "%q\n" "${route_multipath}"
- # Ya tengo los 2 multipath, ahora puedo comparar
- if [[ "$route" != "$route_multipath" ]];then
- # si no son iguales, es hora de cambiar el default gateway
- ip route chg default proto static $multipath
- ip route flush cache
- echo `date`" cambiando default gateway a $multipath" >> /var/log/multipath_watchdog.log
- fi
*/5 * * * * root /usr/local/bin/multipath_watchdog.sh
4.1-El script completo(multipath_watchdog.sh)
Descargar el script completo: multipath_watchdog.sh5-Y las 13 placas?
Estem... estaba esperando que pregunten :-). Si bien al principio pareció complicado, a los dos minutos ya había una solución:placas PCI de 4 ports Funcionan bien, sólo son un poco caras (US$ ~150) comparado con lo que cuestan 4 placas de red(US$ ~36). El tema es que con 3 slots pci tengo 12 placas y yo necesito al menos 14(13 adsl + 1 LAN) y mothers con 4 o 5 solts PCI son un poco difícil de conseguir.Así que si bien sirven, necesitaba encontrar una solución más genérica
Quizá algunos ya se estén preguntando para que necesito 13 placas si los puedo enchufar todos juntos a una sola ethernet y crear 13 alias en el linux, en principio es posible, pero puedo tener mucho más control(y lo necesito) si tengo una interfaz por cada conexión.
5.1-Dicen que el que busca encuentra y así fue: VLAN
VLAN permite crear redes físicas virtuales. Para conectarme desde linux a esa red virtual creo una interfaz que solo escuchará los frames ETHERNET que pertenezcan a esa red virtual.De esta forma usando un switch VLAN de 24 bocas entre los modems/routers y el Linux , puedo decir que un puerto del switch, se mapee con una VLAN específica, y así usar 23 bocas del switch para conectar ADSL y con la boca restante conecto el Linux usando una VLAN por ADSL.
5.2-Configurando el switch VLAN
La idea principal es usar una boca del switch para conectar con el linux, y 13 bocas para conectar los ADSL.En la boca que se conecta con el Linux configuro las VLAN respectivas, tanto en el port del switch, como del lado del Linux para que se puedan hablar.
Pero los modems/routers ADSL no hablan VLAN, por lo que necesito configurarlos de otra manera. Afortunadamente, los switchs VLAN ofrecen 2 modos de operación por puerto: tagged y untagged.
El modo tagged es en el que efectivamente se crea la VLAN en esa boca del switch.
El modo untagged en cambio, bindea un port con un VLAN ID determinado, pero sin requerir que el tráfico llegue con el tag VLAN, es decir el tráfico llega normalmente y es el router quien lo taggea.
Entonces configuro cada uno de los 13 ports de los ADSL "untagged" con su respectivo VLAN ID.
5.3-Configurando Linux para usar VLANs
Del lado del Linux tengo que levantar las VLAN. Para crear VLANs en Linux usamos el comando vconfig (paquete vlan)Para hacerlo bien fácil, integramos la configuración de las VLANs a nuestro script firewall.sh.
Repasemos la parte en donde hacemos un loop por cada conexión y entre otras cosas damos de alta las ips, en este caso, en vez de configurar una interfaz normal, doy de alta una interface VLAN y luego la configuro.
- # por cada conexión
- for ((n=0;n<${#adsl_ifaces[@]};n++)); do
- #...
- # creamos la vlan con su ID respectivo
- vconfig add ${adsl_ifaces[n]%%.*} ${adsl_ifaces[n]##*.} &>/dev/null
- # configuro la ip
- ifconfig ${adsl_ifaces[n]} ${adsl_ips[n]} netmask 255.255.255.0 up
- #...
- done
6-Diagnóstico y debug
Hay varios lugares por donde mirar, y varios comandos que podemos usar para saber si el balanceo esta realmente funcionando o no.Primero usamos ip route para ver cual es nuestro default gateway en el momento actual, deberíamos ver algo así como "nexthop via dev...." por cada una de las conexiones activas
ip route showPara ver la tabla de una conexión en particular.
ip route show table adsl2Para testear una conexión podemos por ejemplo hacer un ping a un google.com usando la ip de la conexión en cuestión.
ping -I 10.0.2.2 www.google.comTambién podemos hacer tests de velocidad sobre una conexión, usando por ejemplo wget o curl.
wget --bind-address=10.0.2.2 http://somehost/somefileUsando tcpdump o iftop podemos verificar que realmente los paquetes estén saliendo por la interfaz(y conexión) correspondiente.
curl --interface 10.0.2.2 http://somehost/somefile
tcpdump -n -i eth0.2Por ultimo si los script nos dan errores podemos ejecutarlos con "bash -x" para ver que comandos son los que están fallando
iftop -n -i eth0.2
bash -x firewall.sh
bash -x multipath_watchdog.sh
7-Algunos puntos en contra
Puede pasar que los modems se cuelguen seguido. Esto pasa porque al usarlos de router, los modems tienen que hacer NAT y connection tracking ellos, y tienen una cantidad de memoria finita asignada para ello. Si usamos esta receta para colgar detrás cientos y cientos de máquinas puede pasar que llenemos esas tablas y los modems se cuelguen, en ese caso hay que conseguir o mejores modems(o usar un mikrotik por ejemplo como cuento más abajo), o codear una nueva solución que contemple pon, poff y la mar en coche.Algunos Home banking muy particulares, usan la ip de origen como una variable más en su esquema de seguridad, el problema es que toman es ip en un servidor que autentica y luego nos hacen saltar a un segundo servidor, pero se nos exige mantener la misma ip de origen, cosa que no podemos garantizar.
8-Conclusión
La solución funciona muy bien, puedo contar varios ejemplos en donde está funcionando, exactamente igual o con diferencias, por ejemplo con el agregado de control de ancho de banda usando htb-gen, o con selección de proveedor o grupos lógicos de proveedores por cliente, o usar por ejemplo un mikrotik de 9 bocas en vez del switch VLAN, para poder tener una tabla de conntrack que se pueda estirar lo que haga falta.También hay empreas que lo único que quieren es que no se corte jamas internet, sea porque usan VPNs con sistemas, o porque su negocio depende mucho del e-mail o de la web, y contratan dos proveedores o más.
Hay muchos escenarios, pero lo fundamental es que esta solución ha sido lo suficientemente flexible y cómoda para adaptarse a todos ellos, y es por eso que hoy la quise compartir con la cosi detta "comunidad”.
Enjoy!
Tomado de: http://bourneagainshell.blogspot.com.ar/2008/05/de-como-conectar-13-adsls-en-balanceo.html
No hay comentarios:
Publicar un comentario