Control de ancho de banda con Linux Traffic Control (tc)

Este artículo tratará acerca de Linux Traffic Control, la herramienta que nos ofrece el kernel de Linux para controlar el uso del ancho de banda cuando usamos este sistema operativo como puerta de enlace para la navegación a Internet, como equipo de ruteo entre diferentes redes corporativas, etc.

Sea como sea, en mi caso lo llegué a utilizar en conjunto con el servicio Squid (sobre el que escribí hace poco aquí), donde aparte de dar servicio de caché de navegación el servidor se encargaba de controlar el ancho de banda de los usuarios. Su implementación como siempre es muy sencilla como lo veremos a continuación.

Las directivas de la herramienta Linux Traffic Control se realizan mediante el comando tc, que se encuentra disponible en el paquete iproute2, en openSUSE específicamente se llama xiproute2 (xLinux network configuration utilities) y se instala por defecto durante la instalación del sistema operativo, solo resta utilizarlo según los siguientes pasos.
  1. Comenzamos por crear un script bash que se encargará de implementar las directivas de control de ancho de banda en el kernel de Linux. Con los siguientes comandos que deben ser ejecutados con el usuario root creamos el archivo tclimitar, le asignamos permisos de ejecución y lo editamos...
    testsrv:~ # touch /bin/tclimitar
    testsrv:~ # chmod 755 /bin/tclimitar
    testsrv:~ # vi /bin/tclimitar

  2. Si queremos controlar solamente la velocidad de bajada podemos utilizar una configuración similar a la que sigue:
    #!/bin/bash

    # Sección 1: Seccion para definicion de variables y limites de ancho de banda
    # mediante comando tc:

    # Definicion de variables:

    INTERFACE="enp2s0"
    IN="tc filter add dev $INTERFACE parent 1:0 protocol ip prio 1 u32 match ip dst "
    PUERTO="match ip sport "
    REGLA="0xffff flowid"

    # Definicion de limites de ancho de banda:
    tc qdisc del dev $INTERFACE root
    tc qdisc add dev $INTERFACE root handle 1: htb default 10
    tc class add dev $INTERFACE parent 1: classid 1:1 htb rate 1024kbit
    tc class add dev $INTERFACE parent 1:1 classid 1:10 htb rate 1024kbit
    tc class add dev $INTERFACE parent 1:1 classid 1:20 htb rate 2048kbit
    tc class add dev $INTERFACE parent 1:1 classid 1:30 htb rate 3072kbit
    tc class add dev $INTERFACE parent 1:1 classid 1:40 htb rate 4096kbit
    tc qdisc add dev $INTERFACE parent 1:10 handle 10: sfq perturb 10
    tc qdisc add dev $INTERFACE parent 1:20 handle 20: sfq perturb 10
    tc qdisc add dev $INTERFACE parent 1:30 handle 30: sfq perturb 10
    tc qdisc add dev $INTERFACE parent 1:40 handle 40: sfq perturb 10

    # Sección 2: Sección de habilitación de dispositivos o redes (respetar orden):
    # Limite individual por equipo (mayor prioridad):

    $IN 192.168.1.20/32 $PUERTO 3128 $REGLA 1:30
    $IN 192.168.1.50/32 $PUERTO 3128 $REGLA 1:40

    # Limite por defecto para todos los equipos de la red (menor prioridad):
    $IN 192.168.1.0/24 $PUERTO 3128 $REGLA 1:20

    # Mensaje de finalizacion en la aplicacion de reglas:
    echo
    echo "Se han aplicado exitosamente las reglas de control de ancho de banda!"
    echo
    A continuación van algunas observaciones que considero de importancia aclarar con respecto al script anterior:
    • El contenido anterior se separa en dos secciones, la primera donde se encuentran predefinidas las variables y las reglas de control de ancho de banda que se suelen editar en pocas ocasiones; y la segunda sección (la que más se va a modificar) donde se aplican las reglas de control de ancho de banda definidas en la primera sección a los equipos o redes que deseamos controlar.
    • El fragmento "default 10" de la directiva "tc qdisc add dev $INTERFACE root handle 1: htb default 10" indica que por defecto todo el tráfico de bajado y subida estará limitado a la regla 1:10, o lo que es lo mismo, a un límite de 1024 kbit/s.
    • Esta versión del script no controla el tráfico entrante, la velocidad de subida máxima estará limitada por el límite de descarga asignado por defecto, al equipo o a la red, es decir si se aplica la regla 1:10 a 1024 kbit, si es 1:20 a 2048.
    • Para limitar todo tipo de tráfico que se dirige al equipo con IP 192.168.1.50 a 2048 kbps (regla 1:20) tenemos que especificar la directiva de la siguiente forma:
      $IN 192.168.1.50/32 flowid 1:20
      Si queremos limitar el tráfico correspondiente al puerto 3128 del servicio Squid la directiva debería ser la siguiente:
      $IN 192.168.1.50/32 $PUERTO 3128 $REGLA 1:20
      También podemos aplicar reglas similares a las anteriores pero a redes de la siguiente forma:
      $IN 192.168.1.0/24 $PUERTO 3128 $REGLA 1:20
    • Al establecer los límites siempre tenemos que colocar en orden primero las de carácter más específicas y luego las menos específicas. En el script primero se definen reglas para una dirección IP (específico) y luego para la red (general) a la cual corresponde esa misma dirección IP.
    • Hay que reemplazar el valor de la variable INTERFACE por la unidad de red que corresponda, en mi openSUSE 13.2 de prueba el nombre de la interfaz era enp2s0.

    Sin embargo, si deseamos poder especificar un límite de subida inferior a un límite de descarga para una misma dirección IP o red ya no basta con el ejemplo anterior, la configuración que permite realizar eso es la siguiente:
    #!/bin/bash

    # ======================================================================================
    # Sección 1: Seccion para definicion de variables y limites de ancho de banda
    # mediante comando tc:
    # ======================================================================================
    # Definicion de variables:

    INTERFACE_IN="enp2s0"
    INTERFACE_OUT="ifb0"
    IN="tc filter add dev $INTERFACE_IN parent 1:0 protocol ip prio 1 u32 match ip dst "
    OUT="tc filter add dev $INTERFACE_OUT parent 1:0 protocol ip prio 1 u32 match ip src "
    PUERTO="match ip sport "
    REGLA="0xffff flowid"

    # Cargar y habilitar interfaces virtuales para ccontrol de uploads. Luego redirigir
    # el trafico entrante de la intefaz fisica a la interfaz virtual.

    modprobe ifb numifbs=1
    ip link set dev $INTERFACE_OUT up
    tc qdisc del dev $INTERFACE_IN root 2>/dev/null
    tc qdisc del dev $INTERFACE_IN ingress 2>/dev/null
    tc qdisc del dev $INTERFACE_OUT root 2>/dev/null
    tc qdisc add dev $INTERFACE_IN handle ffff: ingress
    tc filter add dev $INTERFACE_IN parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev $INTERFACE_OUT

    # Definicion de limites de ancho de banda para download:
    tc qdisc add dev $INTERFACE_IN root handle 1: htb
    tc class add dev $INTERFACE_IN parent 1: classid 1:1 htb rate 1mbit
    tc class add dev $INTERFACE_IN parent 1:1 classid 1:10 htb rate 1mbit
    tc class add dev $INTERFACE_IN parent 1:1 classid 1:20 htb rate 2mbit
    tc class add dev $INTERFACE_IN parent 1:1 classid 1:30 htb rate 3mbit
    tc class add dev $INTERFACE_IN parent 1:1 classid 1:40 htb rate 4mbit
    tc qdisc add dev $INTERFACE_IN parent 1:10 handle 10: sfq perturb 10
    tc qdisc add dev $INTERFACE_IN parent 1:20 handle 20: sfq perturb 10
    tc qdisc add dev $INTERFACE_IN parent 1:30 handle 30: sfq perturb 10
    tc qdisc add dev $INTERFACE_IN parent 1:40 handle 40: sfq perturb 10

    # Definicion de limites de ancho de banda para upload:
    tc qdisc add dev $INTERFACE_OUT root handle 1: htb
    tc class add dev $INTERFACE_OUT parent 1: classid 1:1 htb rate 1024kbit
    tc class add dev $INTERFACE_OUT parent 1:1 classid 1:10 htb rate 1024kbit
    tc class add dev $INTERFACE_OUT parent 1:1 classid 1:20 htb rate 2048kbit
    tc class add dev $INTERFACE_OUT parent 1:1 classid 1:30 htb rate 3072kbit
    tc class add dev $INTERFACE_OUT parent 1:1 classid 1:40 htb rate 4096kbit
    tc qdisc add dev $INTERFACE_OUT parent 1:10 handle 10: sfq perturb 10
    tc qdisc add dev $INTERFACE_OUT parent 1:20 handle 20: sfq perturb 10
    tc qdisc add dev $INTERFACE_OUT parent 1:30 handle 30: sfq perturb 10
    tc qdisc add dev $INTERFACE_OUT parent 1:40 handle 40: sfq perturb 10

    # ======================================================================================
    # Sección 2: Sección de habilitación de dispositivos o redes (respetar orden):
    # ======================================================================================
    # Limite individual por equipo (mayor prioridad):

    $IN 192.168.1.90/32 $PUERTO 3128 $REGLA 1:40
    $OUT 192.168.1.90/32 flowid 1:40

    # Limite por defecto para todos los equipos de la red (menor prioridad):
    $IN 192.168.1.0/24 $PUERTO 3128 $REGLA 1:20
    $OUT 192.168.1.0/24 flowid 1:10

    # Mensaje de finalizacion en la aplicacion de reglas:
    echo
    echo "Se han aplicado exitosamente las reglas de control de ancho de banda!"
    echo
    Como se puede observar este script tiene varias diferencias en comparación al anterior, las mismas se detallan a continuación:
    • En este script se opta por utilizar una interfaz virtual ifb0 a la cual se le redirige el tráfico entrante de la interfaz física, a fin de que se puedan aplicar los mismos tipos de filtros al tráfico entrante como al saliente.
    • Al redirigir el tráfico entrante de la interfaz física a la interfaz virtual ifb0 estamos logrando que las direcciones IP o redes que son filtradas como destino (dst) para el tráfico entrante puedan ser identificadas como origen (src) cuando el tráfico es saliente.
    • Se definen filtros y límites por separados para la interfaz física y la virtual ifb0.
    • El límite 1mbit equivale a 1024kbit.
    • Para limitar la velocidad de descarga se tiene que anteponer la variables $IN y $OUT para limitar la velocidad de subida.
    • Al limitar la velocidad de subida especificando la variable $OUT solo se puede utilizar la regla flowid, ya que la máquina de origen dueña de los paquetes actuará como cliente y utilizará puertos tcp/ip altos (Puertos dinámicos o privados) de forma dinámica para la conexión.
    • No se especifica el fragmento "default 10" en las directivas "tc qdisc add dev $INTERFACE_IN root handle 1: htb" y "tc qdisc add dev $INTERFACE_OUT root handle 1: htb" para evitar problemas con la especificación de límites por defecto. Sin embargo esto nos obliga a especificar un límite por defecto a las redes que estarán utilizando del servicio, de lo contrario tendrán navegación con velocidad ilimitada.

  3. Continuemos. Para liberar los controles de ancho de banda tenemos que crear un script que se encargue de dicha tarea:
    testsrv:~ # touch /bin/tcliberar
    testsrv:~ # chmod 755 /bin/tcliberar
    testsrv:~ # vi /bin/tcliberar

    Editado el archivo le agregamos el siguiente contenido:
    #!/bin/bash

    # Definicion de variables:

    INTERFACE_IN="enp2s0"
    INTERFACE_OUT="ifb0"

    # Liberacion de filtros:
    tc qdisc del dev $INTERFACE_IN root 2>/dev/null
    tc qdisc del dev $INTERFACE_IN ingress 2>/dev/null
    tc qdisc del dev $INTERFACE_OUT root 2>/dev/null

    # Mensaje de culminacion:
    echo
    echo Navegacion liberada.
    echo
  4. Por otro lado, para facilitar la modificación de reglas y el registro de nuevas directivas de limitación a equipos/redes, y posterior implementación automática de las reglas podemos crear otro script que se encargue de ello como se muestra a continuación:
    testsrv:~ # touch /bin/tcmodificar
    testsrv:~ # chmod 755 /bin/tcmodificar
    testsrv:~ # vi /bin/tcmodificar

    Al archivo se le agrega el siguiente contenido que simplemente edita el archivo con Vim y luego lo ejecuta para aplicar los cambios realizados previamente.
    #!/bin/bash
    vim /bin/tclimitar
    /bin/tclimitar
  5. Finalmente ejecutamos el siguiente comando para que durante el arranque del sistema operativo automáticamente se carguen las directivas de control de ancho de banda:
    testsrv:~ # echo "/bin/tclimitar" >> /etc/init.d/boot.local
    Y eso es todo, fácil, apenas me llevó más de tres noches (madrugadas incluidas) hacer las pruebas para verificar que me funcione más o menos bien, sin entrar en detalle en nada..

Fuentes

Comentarios

  1. Hola, muchas gracias por tu aporte, esta muy bueno; quisiera preguntarte porque le asignas un rate a la clase 1:1, no seria suficiente hacerlo en sus clases hijas?

    ResponderEliminar
    Respuestas
    1. "Hay que reemplazar el valor de la variable INTERFACE por la unidad de red que corresponda, en mi openSUSE 13.2 de prueba el nombre de la interfaz era enp2s0." ¿ A que se refiere con eso? ¿Al nombre de punto de acceso? ¿O a wlan1?

      Eliminar
    2. En primer lugar respondo a Anónimo, la verdad no estoy muy familiarizado con la configuración de tc, debo de reconocer que la configuración que tenía era un código legacy y para no perderlo lo documenté en el blog.

      Con respecto a la pregunta de zettkian, si, la interfaz de red que aparece en tu distro Linux,. Normalmente siempre suele ser eht0 o wlan1 como mencionaste, pero no tengo idea porqué en el penSUSE 13.2 que usé para las pruebas asignó un nombre de interfaz tan raro como emp2s0.

      Saludos.

      Eliminar
  2. Saludos. En mi caso, con Linux Mint, al aplicar ifconfig, me sale:

    ens33 Link encap:Ethernet direcciónHW 00:21:97:32:a1:6a
    Direc. inet:192.168.1.106 ....

    ResponderEliminar
    Respuestas
    1. Hola Rafa, pues supongo que para el sistema operativo la interfaz de red se llama ens33, y es ese nombre el que se debería utilizar en vez del nombre enp2s0 que he utilizado en esta mini guía.

      Eliminar

Publicar un comentario