sábado, 24 de mayo de 2008

De como conectar 13 ADSL en balanceo de carga (o Linux load balancing and multipath HOWTO)

Antes que nada para los despistados que caigan acá y estén buscando una solución con un .exe y botón [siguiente]: bueno... buena suerte :-) !

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 banda
geek- bueno... existe speedy 5Megas, <sarcastic> con eso debería bastarle </sarcastic>
dueño: bien, pude funcionar... me llevo 13.
geek: (plop?!)

Esto 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.

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 PCI
empleado(casa de computación)- bien aquí tiene
geek- bien, ahora necesito un mother para usarlas todas juntas
empleado- (plop?!)

Por 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.

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:
  1. Levantar PPPOE con Linux (modem ADSL bridgeando)
  2. Levantar PPPOE con el propio modem (modem ADSL routeando)

En 1 estamos agregando una capa más de complejidad(pon,poff,cambio de IP), y estamos haciendo la solución ADSL-dependiente(o PPP dependiente).

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)

Después comienza realmente el script, así que llegamos a la linea divisoria, si estás buscando una solución Copy and Paste, podés saltar todo esto y luchar directamente con el script completo.

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
  
Por cada conexión necesitamos:
  • 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)
Como dijimos vamos a crear una tabla de ruteo por cada conexión, por una cuestión visual vamos a usar nombres como adsl1,adsl2,etc. para identificar la tabla de cada conexión ADSL.

Pero Linux, internamente, no maneja las tablas de ruteo por nombre sino por número, así que existe un lugar en donde vamos a tener que mapear nuestros nombres de tablas a números. Ese lugar es /etc/iproute2/rt_tables.

El formato es muy sencillo, les dejo un ejemplo:

11 adsl1
12 adsl2
13 adsl3
...
24 adsl14

Ahora nos toca hacer todo lo que enumeramos por cada conexión.

# 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

Ahora viene la parte de configurar iptables, pero, ¿Porqué necesitamos iptables, si multipath es una cuestión de rutas y no de firewall?

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.

Antes de escribir una sola regla de iptables, borro todo lo previo para arrancar de cero, como normalmente uso este archivo para cargar todas mis reglas de firewall esta bien que así sea, porque enseguida las voy a recrear.

############ 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_forward

o mas prolijo en /etc/sysctl.conf:

net.ipv4.ip_forward=1

Y 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.2

Y 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 contras

Uno 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
  
Y eso es todo, solo falta ejecutarlo y tendremos nuestra solución andando.A partir de este punto surgen otros interrogantes, como por ejemplo, como testeo que los enlaces estén funcionando, y como hago para adaptar el multipath en caso de que alguno de los enlaces se caiga. Si siguen leyendo, también hay respuestas a esas preguntas.
3.3-El script completo(firewall.sh)
Descargar el script completo: firewall.sh

4-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

La primer dificultad que surge es como saber si un enlace esta caído o no. Después de rascarme bastante la cabeza dí con una solución que me dejo feliz.

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"
El script hará un ping a cada uno de esos servidores parándose de a un enlace por vez. Al primer pong que recibimos, podemos afirmar con certeza que ese enlace esta up y no me hace falta seguir pingueando el resto.
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
  
Si para un enlace no recibo ninguna respuesta entonces ese enlace lo dejo fuera del multipath. Como un plus, también puedo aprovechar para hacer telnet a ese modem en particular y resetearlo, o mandar un mail avisando que ese enlace esta caído, o incluso hacer sonar la “bell” del servidor si habrá alguien escuchando.
        #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
  
Luego una vez que tengo toda la info estoy listo para tomar decisiones, voy a cambiar el default gateway solo en el caso de que tenga un multipath distinto al actual, ya que si los mismos enlaces que antes están up, no hace falta que modifique nada, y no pago el costo de un flush de la tabla de ruteo por ejemplo.
#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
  
Bien, eso también fue todo, ahora solo hay que agregarlo en alguna crontab para que se ejecute cada unos 5 minutos. Yo uso una crontab propia para esta y otras cosas periódicas que la llamo /etc/cron.d/watchdog
*/5 * * * * root /usr/local/bin/multipath_watchdog.sh
4.1-El script completo(multipath_watchdog.sh)
Descargar el script completo: multipath_watchdog.sh

5-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 show

Para ver la tabla de una conexión en particular.

ip route show table adsl2

Para 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.com

Tambié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/somefile
curl --interface 10.0.2.2 http://somehost/somefile

Usando tcpdump o iftop podemos verificar que realmente los paquetes estén saliendo por la interfaz(y conexión) correspondiente.

tcpdump -n -i eth0.2
iftop -n -i eth0.2

Por ultimo si los script nos dan errores podemos ejecutarlos con "bash -x" para ver que comandos son los que están fallando

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!

55 comentarios:

excelente trabajo, estaria bueno que publiques las pruebas y resultados, %utilizado, caidas, etc.

Excelente trabajo estimado, me imagino las horas colgao investigando que pasaste.

Muy buen Articulo. Excelente Se agradece su publicación.

y Viva el Sofwtare libre.

Hola Luciano, realmente fascinante tu laburo. Yo hace mucho tiempo que venia probando y buscando diferentes scripts y la verdad lo tuyo me asombro. Me gustaria ponerme en contacto con vos para compartir ideas, ya que yo tengo desarrollado un script de firewall y qos en perl, y me gustaria mucho agregarle lo tuyo para darle el toque final.
Te invito a ver mi blog donde publique el script y me gustaria saber tu opinion del mismo y si se le pueden hacer retoques.
Saludos!!

Impresionante, soy poseedor de una script similar hecha por mi tambien para un hardy pero creo que con tu solución arreglaré algunos problemas, voy a mirarla a fondo, muchas gracias de antemano.
porcierto yo tambien uso el ubuntu 8.04 ue que se ven en tus screens xd

Un artículo muy interesante. Gracias por compartirlo, Luciano! Llegué aquí gracias al link que me pasó Diego Woitasen por la lista lugar-conf.

Ahora bien, quería hacerte unas consultas. Ahora que tuve un poco más de tiempo, estuve analizando ambos scripts y haciendo algunas pruebas y noté que en el multipath_watchdog.sh parece haber un error en la parte donde se comprueban cada uno de los enlaces, ¿puede ser? Porque cuando falla el ping, se agrega una ruta nueva con el enlace que tiene respuesta. Al menos ese es el comportamiento que estoy observando. Entonces, bajo esas circunstancias, sigue existiendo la posibilidad de que salga por la ruta que no funciona.

Daniel, el script es de producción, los primeros días tuvo un pequeño error de sintaxys (un < cambiado por un >), debido a que la versión que subi al blog está más frondosamente documentada y por ende toqueteada, que la de producción.

La funcionalidad del script es la esperada, para cada enlace pinguea a varios servers, conque sólo uno le respoda ese enlace se agrega a lo que llamamos multipath, luego se repite la operación para cada enlace.
El multipath final que me queda (todos los enlaces que tuvieron respuesta), lo comparo con el actual y si es igual no hago ningún cambio.
Si el multipath que conseguí es distinto del actual, entonces actualizo la ruta por defecto con mi nuevo multipath.

O tenés la primera versión con el error en el caracter ">" por "<", o estás usándolo mal sino estás obtieniendo la funcionalidad que menciono arriba.

Hola Luciano. Gracias por responder.

Creo que ya encontré cual es el problema: la variable $multipath. En el script multipath_watchdog.sh si pongo un echo $multipath inmediatamente debajo de multipath_total=0 ya ahí veo que tiene algún valor que es justamente el que se está trayendo del script firewall.sh. Luego, dentro del for más interno (el segundo), al hacer la comparación y estar las dos interfaces arriba, se agregan esas dos rutas a la variable $multipath, quedando finalmente cuatro rutas en ella. Entonces la comparación falla y quedan estas cuatro rutas.

Daniel, si te fijas en firewall.sh en la linea 14 esta esto:
test "$1" == "loadvars" && return 0

Lo cual garantiza que solo se cargan las variables adsl_* y ninguna linea más se ejecuta. Por lo que multipath tendría que llegar vacío. Podes poner un "set -x" en firewall.sh y ver porque no está saliendo cuando llega a esa linea, o quizas borraste esa linea sin querer.

Hola Luciano. Gracias por responder. Te cuento que había comentado esas primeras líneas y ahí estaba mi problema. Ahora parece funcionar bien aunque estoy notando un problemita que creo que es por un tema de resolución de DNS. El tema es que para hacer las pruebas tengo una de las interfaces con gateway una máquina de mi red interna con GNU/Linux, Shorewall/DNSMasq, y la otra interfaz tiene como gateway un router Cisco. Puse en el resolv.conf las IPs de la máquina de la red local con DNSMasq y la IP del DNS público para la otra interfaz y si bien cuando desconecto la interfaz con la IP interna me sigue respondiendo la de la IP pública probando con ping, tarda varios segundos por cada pong a pesar que se muestran tiempos de respuesta del orden de los cientos de milisegundos. Esto me huele a que es por algún tema de reversos...

Buenísimo que ahora te funcione, respecto a lo del ping que comentas, exactamente es un DNS reverso que se hace por cada ping, pero eso se puede evitar agregando -n. De hecho ahora lo voy a agregar al script porque no tiene sentido hacer DNS reverso en este contexto.
Lo que yo hago cuando tengo multipath es usar bind(sin forwarders) y apuntar /etc/resolv.conf a 127.0.0.1, de esa forma resuelvo via el enlace que este up no importa si se van cayendo y vuelven.
Muchas gracias por tu aporte.

De nada. Te referís a instalar un servidor Bind en la misma máquina que hace el balanceo? Te parece que vale la pena? Y cómo haces la configuración sin forwarders? De todas maneras, aún usando la opción -n, al hacer el ping sigo observando esta latencia que te mencionaba. Pero lo curioso es que solo la observo cuando resuelvo con el servidor DNS público (sale por la IP pública). Con la salida por la IP interna no tengo ese problema (resolviendo por el servidor DNS de la LAN). Será porque al intentar resolver el reverso de la máquina que hace el multipath en el servidor DNS público no lo encuentra?

Por otro lado, me gustaría consultarte cómo combinaste esta configuración de multipath con las reglas de filtrado propiamente dichas. Usaste shorewall junto al script y lo configuraste de alguna forma para que interactúe con él o armaste las reglas "a pelo" en otro script :-)? En un principio, las reglas para las interfaces con los ADSL estimo que van a ser las mismas, supongo. La idea que tenía pensada era dejar la máquina con GNU/Linux y multipath haciendo de firewall y NATeando por las dos interfaces ADSL haciendo también el NAT en ella misma.

Gracias por tu tiempo.

hol, no me dejas tu mail o msn para contactarme con vos?
saludos

Podes encontrarme en:
lruete [arroba] sequre.com.ar

PARABEMS PELO MATERIAL APRESENTADO

O HOMEM INTELIGENTE É AQUELE QUE SE FAZ DE IDIOTA DIANTE DE UM IDIOTA QUE SE PASSA POR INTELIGENTE.

SOLUCAO PROCURADA POR MILHARES, E TAO POUCOS SE DISPOE A APRESENTAR ALGO COMO SUA DICA E SCRIPTS MASTIGADOS.

Marcelo Nunes - Goiania - Goias Brasil
netdovale@hotmail.com

Tu script es excelente felicitaciones,
mi unico gran problema hasta ahora es que no puedo redireccionar correctamente de las wan a un PC de la red interna, estoy balanceando directamente con pppoe, lo que pasa es que no marca los paquetes que vienen desde el exterior y al responder envía la respuesta por otra wan, bueno hay veces que coinciden, pero son muy pocas. como puedo solucionar eso, por que según el script debería marcar los paquetes.

si puedes contactarme para ayudarme a aky(dot)info(at)gmail(dot)com gracias.

pd. revisare tu blog lo más seguid posible para ver si encuentras una solución xD

Muy interesante tu script, agradecimientos por publicarlo.
En nuestro caso no hemos tenido mucho éxito con él y hemos debido desistir. Nuestra conf sobre Ubunut 8.10 es la de 2 ifaces de red con una IP cada una conectada cada una a a su router en el que todo el NAT está desviado a su correspondiente IP en el server Linux.

Cuando lo ejecutamos (desde el arranque para asegurarnos que no hubiera otras reglas) El comportamiento es del todo aleatorio, como si alguno de los balanceos no consiguiera enrutar. Además tenemos el problema mencionado de los pings desde la maquina cuando lo hacemos a través de cualquier de las 2 IPs, llega el primer paquete y el resto no llega o se demora.

He hecho un volcado de la ejecución y nuestro iptables (con nuestras propias reglas de firewall) por si alguien es capaz de darnos alguna orientación que agradeceremos mucho pues hemos acabado con script más modesto, pero también interesante en: http://www.initzero.it/portal/it/products/opensource/izbalancing pero que usa un metodo "equalize" en las rutas que entiendo nos nos permite elegir pesos y además evidentemente no es tan completo y bien elaborado.


+ test debug == show
+ test debug == loadvars
+ (( n=0 ))
+ (( n<2 ))
+ ifconfig eth1 192.168.0.61 netmask 255.255.255.0 up
+ ip route flush table adsl1
+ ip rule del from 192.168.0.61 table adsl1
+ tc qdisc del dev eth1 root
+ tc qdisc add dev eth1 root tbf rate 512kbit latency 50ms burst 1540
++ /sbin/ip route ls table main
+ read line
+ test -z '192.168.0.0/24 dev eth1 proto kernel scope link src 192.168.0.61'
+ test -z '192.168.0.0/24 dev eth1 proto kernel scope link src 192.168.0.61'
+ ip route add 192.168.0.0/24 dev eth1 proto kernel scope link src 192.168.0.61 table adsl1
+ read line
+ test -z '192.168.0.0/16 dev eth0 proto kernel scope link src 192.168.0.62'
+ test -z '192.168.0.0/16 dev eth0 proto kernel scope link src 192.168.0.62'
+ ip route add 192.168.0.0/16 dev eth0 proto kernel scope link src 192.168.0.62 table adsl1
+ read line
+ test -z ''
+ continue
+ read line
+ ip route add default table adsl1 proto static via 192.168.0.101 dev eth1
+ ip rule add from 192.168.0.61 table adsl1
+ multipath=' nexthop via 192.168.0.101 dev eth1 weight 3'
+ (( n++ ))
+ (( n<2 ))
+ ifconfig eth0 192.168.0.62 netmask 255.255.255.0 up
+ ip route flush table adsl2
+ ip rule del from 192.168.0.62 table adsl2
+ tc qdisc del dev eth0 root
+ tc qdisc add dev eth0 root tbf rate 512kbit latency 50ms burst 1540
++ /sbin/ip route ls table main
+ read line
+ test -z '192.168.0.0/24 dev eth1 proto kernel scope link src 192.168.0.61'
+ test -z '192.168.0.0/24 dev eth1 proto kernel scope link src 192.168.0.61'
+ ip route add 192.168.0.0/24 dev eth1 proto kernel scope link src 192.168.0.61 table adsl2
+ read line
+ test -z '192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.62'
+ test -z '192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.62'
+ ip route add 192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.62 table adsl2
RTNETLINK answers: File exists
+ read line
+ test -z ''
+ continue
+ read line
+ ip route add default table adsl2 proto static via 192.168.0.100 dev eth0
+ ip rule add from 192.168.0.62 table adsl2
+ multipath=' nexthop via 192.168.0.101 dev eth1 weight 3 nexthop via 192.168.0.100 dev eth0 weight 2'
+ (( n++ ))
+ (( n<2 ))
+ ip route del default
+ ip route add default proto static nexthop via 192.168.0.101 dev eth1 weight 3 nexthop via 192.168.0.100 dev eth0 weight 2
+ ip route flush cache
+ iptables -F
+ iptables -X
+ iptables -t nat -F
+ iptables -t nat -X
+ iptables -t mangle -F
+ iptables -t mangle -X
+ (( n=0 ))
+ (( n<2 ))
+ iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
+ (( n++ ))
+ (( n<2 ))
+ iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+ (( n++ ))
+ (( n<2 ))
+ iptables-restore /etc/iptables.up.rules
+ iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
+ iptables -t mangle -N my_connmark
iptables: Chain already exists
+ iptables -t mangle -A FORWARD -m mark --mark 0 -j my_connmark
+ iptables -t mangle -A FORWARD -j MARK --set-mark 0x0
+ iptables -t mangle -A my_connmark -o eth1 -j RETURN
+ (( n=0 ))
+ (( n<2 ))
+ iptables -t mangle -A my_connmark -o eth1 -j MARK --set-mark 0x1
+ iptables -t mangle -A my_connmark -i eth1 -j MARK --set-mark 0x1
+ (( n++ ))
+ (( n<2 ))
+ iptables -t mangle -A my_connmark -o eth0 -j MARK --set-mark 0x2
+ iptables -t mangle -A my_connmark -i eth0 -j MARK --set-mark 0x2
+ (( n++ ))
+ (( n<2 ))
+ iptables -t mangle -A my_connmark -j CONNMARK --save-mark
+ (( n=0 ))
+ (( n<2 ))
+ ip ru del fwmark 0x1 table adsl1
+ ip ru add fwmark 0x1 table adsl1
+ (( n++ ))
+ (( n<2 ))
+ ip ru del fwmark 0x2 table adsl2
+ ip ru add fwmark 0x2 table adsl2
+ (( n++ ))
+ (( n<2 ))

Después de esto nuestras reglas IPTABLES completas on:
# Generated by iptables-save v1.4.0 on Wed Dec 3 11:46:03 2008
*mangle
:PREROUTING ACCEPT [1851:213949]
:INPUT ACCEPT [1776:208223]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [2101:1106422]
:POSTROUTING ACCEPT [2123:1108824]
:my_connmark - [0:0]
-A PREROUTING -j CONNMARK --restore-mark
-A PREROUTING -i eth0 -m mark --mark 0x0 -j MARK --set-mark 0x64
-A PREROUTING -i eth1 -m mark --mark 0x0 -j MARK --set-mark 0x65
-A PREROUTING -j CONNMARK --restore-mark
-A PREROUTING -j CONNMARK --restore-mark
-A FORWARD -m mark --mark 0x0 -j my_connmark
-A FORWARD -j MARK --set-mark 0x0
-A FORWARD -m mark --mark 0x0 -j my_connmark
-A FORWARD -j MARK --set-mark 0x0
-A POSTROUTING -o eth0 -m state --state NEW -j MARK --set-mark 0x64
-A POSTROUTING -o eth1 -m state --state NEW -j MARK --set-mark 0x65
-A POSTROUTING -m state --state NEW -j CONNMARK --save-mark
-A my_connmark -o eth1 -j RETURN
-A my_connmark -o eth0 -j MARK --set-mark 0x1
-A my_connmark -i eth0 -j MARK --set-mark 0x1
-A my_connmark -o eth1 -j MARK --set-mark 0x2
-A my_connmark -i eth1 -j MARK --set-mark 0x2
-A my_connmark -j CONNMARK --save-mark
-A my_connmark -o eth1 -j RETURN
-A my_connmark -o eth1 -j MARK --set-mark 0x1
-A my_connmark -i eth1 -j MARK --set-mark 0x1
-A my_connmark -o eth0 -j MARK --set-mark 0x2
-A my_connmark -i eth0 -j MARK --set-mark 0x2
-A my_connmark -j CONNMARK --save-mark
COMMIT
# Completed on Wed Dec 3 11:46:03 2008
# Generated by iptables-save v1.4.0 on Wed Dec 3 11:46:03 2008
*nat
:PREROUTING ACCEPT [68:5016]
:POSTROUTING ACCEPT [93:6442]
:OUTPUT ACCEPT [108:7810]
-A PREROUTING -p tcp -m tcp --dport 554
-A PREROUTING -p tcp -m tcp --dport 87 -j DNAT --to-destination 192.168.0.61:80
-A PREROUTING -p udp -m udp --dport 50000:50999
-A POSTROUTING -p tcp -m tcp --sport 554
-A POSTROUTING -p udp -m udp --sport 50000:50999
-A POSTROUTING -o eth0 -m mark --mark 0x64 -j SNAT --to-source 192.168.0.62
-A POSTROUTING -o eth1 -m mark --mark 0x65 -j SNAT --to-source 192.168.0.61
-A OUTPUT -s 192.168.0.90/32 -p tcp -m tcp
COMMIT
# Completed on Wed Dec 3 11:46:03 2008
# Generated by iptables-save v1.4.0 on Wed Dec 3 11:46:03 2008
*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT DROP [0:0]
:IPS_ADMITIDAS - [0:0]
-A INPUT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m tcp -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p udp -m udp -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 25 -j ACCEPT
### .. borrados otros puertos admitidos con la misma configuracion
-A INPUT -p icmp -j ACCEPT
-A INPUT -j IPS_ADMITIDAS
-A OUTPUT -o lo -j ACCEPT
-A OUTPUT -j ACCEPT
-A IPS_ADMITIDAS -s 192.168.0.0/24 -j ACCEPT
### .. borradas otras IPs admitidas con la misma configuracion


EL IP ROUTE nos devuelve
# Completed on Wed Dec 3 11:46:03 2008
192.168.0.0/24 dev eth1 proto kernel scope link src 192.168.0.61
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.62
default proto static
nexthop via 192.168.0.101 dev eth1 weight 3
nexthop via 192.168.0.100 dev eth0 weight 2

No tuve mucha suerte con mi anterior consulta. No me extraña dado el tamaño!!.

Seguimos "peleando" con el tema aunque hemos conseguido avanzar.

Un consulta concreta. El script genera por cada interface algo como:

ip route add 192.168.0.0/24 dev eth1 proto kernel scope link src 192.168.0.61 table adsl1
ip route add 192.168.0.0/16 dev eth0 proto kernel scope link src 192.168.0.62 table adsl1

ip route add default table adsl1 proto static via 192.168.0.101 dev eth1

Pero no entiendo porque. Quiero decir no sería suficiente con añadir la ultima ruta default a esas tablas de modo que que si una regla se cumple el paquete salga por ahí. Para que las dos primeras rutas fijas a la tabla?

Gracias anticipadas. Jose Luis.

Este comentario ha sido eliminado por el autor.

En tu post anterior y en este hay algo mal en tu setup ya que las dos redes que quieres balancear se solapan en sus netmask
-192.168.0.0/24
-192.168.0.0/16

Lo cual va a confundir obviamente a la tabla de ruteo a menos que uses el parametro "onlink" para crear la ruta. Lo que te estoy diciendo es algo muy básico de networking y subneting.No tiene sentido, ya que una se pisa con la otra.

Respecto a tu pregunta concreta, las rutas estáticas se copian a las tablas creadas para el balanceo para routear el packete en la "vuelta", es decir el paquete que salio por la tabla adsl1, cuando vuelve conntrack le reestablece la MARKa en prerouting y cuando llega al routeo buscara su ruta en la tabla adsl1 y en esta tabla tiene que encontrar su respuesta, por eso es que las rutas se copian allí.

Luciano, tengo una consulta para hacerte. En todos los casos que pude probar el script me encontré con el comportamiento de que frente a un evento de cambio (up o down) de un enlace se reliaza un flush de la cache. Esto impacta en las conexiones que actualmente se tienen establecidas (Home Banking).
¿Es este comportamiento correcto?
Desde ya muchas gracias.
Gustavo.

@Gustavo la respuesta corta es que las conexiones establecidas no se pierden a pesar del flush, porque el flush es sobre la tabla de ruteo y no sobre conntrack.

Profundizando te puedo decir que el flush del cache de ruteo es absolutamente necesario para que no haya rutas por una conexión que ya no esta up. Pero eso es independiente del tracking de conexiones que hace conntrack, que _no_ es flusheado.
Entonces una conexión ya establecida saldrá si o si por el enlace que usaba anteriormente, aún si la ruta no está cacheada y la tiene que buscar de nuevo va a seguir saliendo por el mismo enlace.

Espero que se haya entendido.

Este comentario ha sido eliminado por el autor.

buenas. Tengo un pequeño problema una ves activo el script al rato me deja de ir internet y no tengo ni idea que es lo que pasa, alguna sugerencia?

@Ezequiel "no me anda internet" no es el tipo de diagnóstico que espero de alguien que quiere hacer andar multipath en linux.
Para ayudarte hace falta información más técninca como la salida de "ip ro" al momento del fallo, para dar un ejemplo.

Disculpa por esa explicacion estaba con prisas :P.
el tema es el siguiente.
Configure el Script con mis ips de mis 2 adsl Tengo una adsl que sale por eth1 y otra eth2 la eth0 es nuestra lan.
Apartir de aqui ahora mismo usamos el servidor como firewall y podemos navegar atraves de la eth1, disponemos de varias conexion que quisieramos agregar por si se cae una linea poder salir por otra, basicamente es nu estro objetivo.
Ahora con tu script en cuanto lo activo aparentemente funciona todo bien por ej cuando lo active la primera ves estubo unos 5 minutos funcionando todo, despues de esos 5 minutos dejo me echo de mi conexion remota y dejo de responder el server entre por LAN y trate de hacer ping a algun servidor externo sin ningun resultado.
A grandes rasgos esto es lo que me pasa, necesitas alguna otra info?

Gracias y disculpa el mensaje de antes :)

anubis:~# ip ro
192.168.1.0/24 dev eth2 proto kernel scope link src 192.168.1.33
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.10
62.57.33.0/24 dev eth1 proto kernel scope link src 62.57.33.97
192.168.9.0/24 dev eth0 proto kernel scope link src 192.168.9.10
default proto static
nexthop via 62.57.33.1 dev eth1 weight 1
nexthop via 192.168.1.1 dev eth2 weight 1

Muy buen articulo. Estoy haciendo pruebas, quiero cambiar las ips 10.0.0.2 por el rango 192.168.0.2, pero cuando lo hago nos devuelve un error (rtnetlink answer: no such process), a ver si puedes ayudarme.
Salu2

Luciano,

Estoy utilizando tu script. Muchas gracias por el aporte.

Cuando solo queda un link activo tuve que quitar/agregar espacios para que los strings de la ruta default configurada y calculada sean iguales:

# Borrar un espacio ANTES de "nexthop", "dev". Agregar espacio ANTES de "proto".

route_multipath=${multipath# nexthop }
route_multipath=${route_multipath% weight*}
route_multipath=" default ${route_multipath/ dev/ dev} proto static"

Quizas se deba a que haya cambiado el formato de salida de "ip ro ls".

Saludos
PS: Sin el tag "pre" no se entiende nada! Aca va el parche:
Con respecto al original:
- se agregó un espacio entre el "#" y "nexthop"
- se agregó un espacio antes del primer "dev"
- se agregó un espacio antes de "proto".

No lo probé, pero otra solución sería:

# Get rid of double spaces
route=${route/ / }
route_multipath=${route_multipath/ / }

#printf "Old: %q\n" "${route}" >> /var/log/adsl_watchdog.log
#printf "New: %q\n" "${route_multipath}" >> /var/log/adsl_watchdog.log

Dice, en ambas sustituciones:

Barra Espacio Espacio Barra Espacio }

que tal, muy bueno el script, en realidad no estoy usando uno igual, pero me base mucho para hacer el mio, ya que me pasaba eso de que se cortaba el msn, las vpn, etc, entonces hice las marcas con iptables y anda perfecto

pero en esa misma pc tengo un proxy (con squid) y cuando pongo la regla que redirecciona al port 3128, no me navega, el ping obviamente si vuelve, pero la "pagina" no, parece que el paquete no sabe por donde volver

router-arg ~ # ip ru ls
0: from all lookup local
32754: from all fwmark 0x2 lookup net1
32755: from all fwmark 0x1 lookup net2
32756: from yyy.yyy.yyy.yyy lookup net1
32757: from xxx.xxx.xxx.xxx lookup net2
32766: from all lookup main
32767: from all lookup default

router-arg ~ # iptables -t mangle -L -nv
Chain PREROUTING (policy ACCEPT 375K packets, 221M bytes)
pkts bytes target prot opt in out source destination
310K 203M CONNMARK all -- * * 0.0.0.0/0 0.0.0.0/0 CONNMARK restore

Chain INPUT (policy ACCEPT 36440 packets, 7503K bytes)
pkts bytes target prot opt in out source destination

Chain FORWARD (policy ACCEPT 338K packets, 213M bytes)
pkts bytes target prot opt in out source destination
285K 197M my_connmark all -- * * 0.0.0.0/0 0.0.0.0/0 MARK match 0x0
285K 197M MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK set 0x0

Chain OUTPUT (policy ACCEPT 34874 packets, 8559K bytes)
pkts bytes target prot opt in out source destination

Chain POSTROUTING (policy ACCEPT 372K packets, 222M bytes)
pkts bytes target prot opt in out source destination
308K 203M CONNMARK all -- * * 0.0.0.0/0 0.0.0.0/0 CONNMARK save

Chain my_connmark (1 references)
pkts bytes target prot opt in out source destination
165K 182M RETURN all -- * eth0 0.0.0.0/0 0.0.0.0/0
33844 3779K MARK all -- * eth1 0.0.0.0/0 0.0.0.0/0 MARK set 0x1
0 0 MARK all -- eth1 * 0.0.0.0/0 0.0.0.0/0 MARK set 0x1
80900 8091K MARK all -- * eth2 0.0.0.0/0 0.0.0.0/0 MARK set 0x2
0 0 MARK all -- eth2 * 0.0.0.0/0 0.0.0.0/0 MARK set 0x2

si nececitas algo mas, avisame

upa, ahi lo hice andar, me habia olvidado de estas 2 lineas:

ip ro add default table $T1 proto static via $GW1 dev $IF1
ip ro add default table $T2 proto static via $GW2 dev $IF2

Hola...

Luciano te hago una par de consultas.
Estoy mirando 2 versiones de tus (hijos) htb-gen, el htb-gen-0.9B-1 y el htb-gen-0.9b
1) Veo unas diferencias el primero tiene para manejar ip publica y mac
te molesta decirme que no entiendo que funcion cumplen esos 2 items?
2)en htb-gen-0.9b se puede forwardear puertos por la web? como seria?

Gracias de antemano.

Luciamo, Muy buen trabajo. Me base en tu proyecto para una solucion similar, pero haciendo la conexion de los adsl en el mismo linux. La consulta es: Cual seria la mejor manera de poder derivar, por ejemplo, todo el trafico de correo (puertos 25 y 110) por una conexion determinada. Intenté utilizar el marcado de paquetes e ip ru, pero creo que se hace lio con el marcado que utiliza el script para mantener la correspondencia de paquetes y no funcionó. Gracias por cualquier idea que puedas aportar. Saludos!

@leonardo lo podes hacer en dos lugares, en OUTPUT para el tráfico del propio server y en PREROUTING para el tráfico de los clientes.
Te muestro con el puerto 25 y para mandarlo siempre por el primer enlace configurado:

iptables -t mangle -I OUTPUT -p tcp --dport 25 -j MARK --set-mark 0x01
iptables -t mangle -I OUTPUT -p tcp --dport 25 -j CONNMARK --save-mark
iptables -t mangle -I PREROUTING -p tcp --dport 25 -j MARK --set-mark 0x01
iptables -t mangle -I PREROUTING -p tcp --dport 25 -j CONNMARK --save-mark

Hola, como estás, leí tu post y me parece EXCELENTE, de verdad, muy bueno y nos va a servir un montón.

Ahora, se me asoma una duda, sobre esto:

Y 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.2


Podrías explicar bien la parte del Pseudocodigo?, es decir, eso hay que introducir en el router zyxel desde la consola de comandos?, y si fuera en otros modelos, como se haría?, y el tema de las máscara, tiene que ser /16 porque no /24 si mis clientes tienen esa máscara?. Que pasa si no realizo ese paso en los routers?. Saludos y Felicitaciones.

P.D.: Tienes planes de hacer esta configuración pero usando pppoe en el linux?, quizás usando tkpppoe?, realizaste el post "Ver gráficos del uso parcial y total (queda para otro post)"?.

Luciano, gracias por tu anterior respuesta. Sabes que por algún motivo que aún no logro descifrar es como que los paquetes se vuelven a marcar con las marcas para la correspondencia de los enlaces haciendo que los puertos elegidos (25 y 110) sigan saliendo por las 2 conexiones (en mi caso) aleatoriamente. El trafico que intento derivar por la primer conexion es el de la LAN:
Ejemplo SMTP:

iptables -t mangle -I PREROUTING -p tcp --dport 25 -j MARK --set-mark 0x01
iptables -t mangle -I PREROUTING -p tcp --dport 25 -j CONNMARK --save-mark

Gracias!

@Julio_Cesar las rutas hacia la LAN són solo necesarias sino quieres hacer dos veces NAT (una en el server y otra en los routers). Sino agregas la linea todo va a funcionar igual. La manera de agregarla en los zixel via la consola de telnet es a travez de:
"Static Routing Setup"->"IP Static Route"
(12->1 en números)

La solución con pppoe ya la tengo funcionando en un server, pero agrega bastante complejidad al script y realmente va a oscurecer más de lo que va a aclarar postearla.

Respecto al post de los gráficos, no, todavía no está hecho.

Luciano, creo que logre resolver el problema. Las reglas van luego de la regla que restaura las marcas en PREROUTING.
Saludos.

Hola Luciano.
Primero que nada gracias por el como.
Tengo una cuestion que no se si esta dentro del mismo hilo.
Este mismo script lo podriamos usar para trabajar con un proxy cache transparente, para poder decidir que usuraio sale por el proxy y que usuario sale derecho a internet?

Te felicito por tu trabajo, tengo una pregunta.
en el caso de ADSL con ip dinamica como seria el cambio a la hora de tomar las ip y las rutas por dhcp?

Hola Luciano.

Tienes algun mail para hacerte unas consultas?

Muchas Gracias.

Saludos.-

Hola Luciano, felicitaciones por tu post.

Te consulto, este tipo de soluciones puede generar algun incoveniente a la hora de hacer port forwarding a una ip interna de mi red?

Digo, supon que con tu solución tengo un excelente router hacia internet, si detras de este router quiero tener una DMZ con Apache o alguna aplicación asi, puedo tener problemas por el hecho de que mis conexiones esten trabajando de esta manera?

Muchas Gracias.

Saludos.-

Felicitaciones compañero!

Muy buen trabajo!

Estoy haciendo mis primeras pruebas.

Te consulto, como quedaría el archivo /etc/network/interfaces?

Me refiero a si debo especificar un gateway para cada interface en dicho archivo?

Muchas Gracias..-

Hola, muchas gracias por compartir tu trabajo con la comunidad. Es perfecto. Lo he combinado con el script de firewall de Bob Sully "firewall.iptables" http://www.malibyte.net/iptables/scripts/fwscripts.html y he conseguido lo que quería. Lo tengo en producción con una conexión simétrica de 2MBps y 2 ADSL de 3MB, y consigo exprimirlas al máximo.

un saludo.

Luis

La verdad me quede sorprendido con tu trabajo recien lo vi, desde ya muchas gracias por el esfuerzo realizado ... Una consulta: Tengo trabajando un servidor pero las rutas las especifico manualmente sin balanceo y para eso utlizo las MARKas, el problema esta cuando reenvio en PREROUTING al squid (proxy) transparente y es como q se pierde la asociacion de MARKas, noce si me explico, porque sin el proxy funciona bien. Puede ser?. TE AGRADEZCO LA VERDAD CON TU SCRIP APRENDI MUCHO.

hola luciano muy bueno tu script te comento que soy un poco nuevo en esto y estoy tratando de sumar una linea adsl con speedy que esta adelate de un roter y otra fibertel que entra directo desde el modem la cuestion es que no consigo hacerlas funcionar las 2 lineas jutas logro poner las placas como esta en el script pero luego no tengo internet es mas el modem de speedy se planta y tampoco logro poner a router la pc que esta como servidor. es desir entro a dos placas de red con speedy que esta saliendo por un router y en la otra placa entro con fibertel y la tercera placa que sobra la uso para distribuirla con un router.. mi pregunta es si se puede hacer funcion asi el balanceo?

antetodo gracis y disculpa la molestia

Holas..
he tomado parte del script y lo he echo funcionar con squid, (proxy transparente) haciendo lo mismo que se hacia con el forward y el prerouting, (en este caso, entre output y postrouting) y además fucionandolo con thunder3 y videocach..^^
bueno.. el problema que tengo es que aveces cuando tengo un coneccion por un adsl, y quiero hacer otra coneccion, esta no puede salir por la otra, parece que algo se lo impide, veo la velocidad de descarga de esta otra interfaz (con vnstat), y noto que no puede traficar en esta otra o es muy..lenta.

otra cosa es que aveces se me caen las interfaces, sobre todo las que estan ociosas, por que puede pasar esto?... y por que dices que seria mejor un microtic para soportar el tráfico?, yo tengo 2 routers para mis 2 adls, y parece que cuando hago balanceo estos se caen... , a alguien le pasa lo mismo???
saludos.!!

Hola luciano. Me gustaria que me ayudaras a configurar un squid como balanceador de carga, proxy bridge transparente y a la vez proxy caché. Todo eso es posible? mi mail mad_skin@hotmail.com muchisimas gracias.

Muy pero muy buen trabajo.. estoy analizandolo poco a poco por que realmente esta muy bien hecho.. pero pues soy algo nuevo en esto y en el firewall.sh estoy obteniendo un error "syntax error near unexpected token '<'" es la linea 33 donde dice <(/sbin/ip route ls table main)y no eh podido corregirlo.. si alguien pudiera ayudarme se lo agradeceria mucho

Es muy placentero saber que desde hace tiempo ya publicado tu articulo aun siga siendo de utilidad para muchas personas en el ambito profesional o educativo. Desde Venezuela y de corazon se te agradece mucho que puedas compartir estas horas de trabajo con toda la comunidad de software libre. Felicitaciones.

Muy bueno, la verdad buscaba otra cosa y quede absorto, muy lindo la verdad, ojala los mios quedaran asi de prolijitos. :P
Abrazo y muchas gracias por el tiempo que le dedicaste

Hola! ..

excelente script, muy buena solución, lo estoy utilizando en el gateway de mi oficina, y es increíble ...

el único problema es que no puedo subir archivos de mas de 700kb via http, a través de conexiones ftp y scp no hay problema, pero via browser se interrumpe el upload ...

Saludos

Hola estoy provando tu script pero al ejecutarlo me da error de sintaxis en la linea 33 '<(/sbin/ip route ls table main)'
indicandome el '<' como elemento inesperado

ya probe quitando el < y lo único que consigo es que el siguiente simbolo o palabra sea la del error

que debo hacer?

gracias

Excelente trabajo!!!
Lo vamos a empezar a probar.
Si tienes en algun lugar los Scrips con la inclusión de pon y poff para los ADSL, sería buenísimo, ya que pensamos hacer un balanceo con 4 adsl y queremos evitarnos las colas delos routers para que no se cuelguen.
Gracias
un abrazo