FreeBSD incluye tres firewalls diferentes en el sistema base (¡In your face Linux!): PF, IPFW e IPFILTER (también conocido como IPF). Cada firewall utiliza reglas para controlar el acceso de paquetes desde y hacia el sistema FreeBSD, aunque cada uno lo hace de diferente forma y con diferentes reglas. Además FreeBSD provee dos traffic shapers para controlar el uso de ancho de banda: altq y dummynet. Tradicionalmente ALTQ está fuertemente ligado a PF, mientras que dummynet a IPFW.

FreeBSD provee múltiples firewalls para cumplir los diferentes requerimientos y preferencias de una amplia variedad de usuarios (de esto se trata la libertad de elección, lo mismo que ha hecho la comunidad GNU/Linux con systemd). Por ende cada usuario debe evaluar qué firewall se adopta mejor a sus necesidades.

En este artículo se explica cómo implementar traffic shaping en FreeBSD utilizando dummynet e IPFW. No voy a hacer una introducción ni a explicar el funcionamiento de IPFW porque un firewall es un mundo, se necesitan varios artículos para cubrir cada aspecto de un firewall (y más uno tan completo que hasta incluye packet shaping entre sus funcionalidades). Notarán que este artículo es bastante extenso y apenas es una introducción a una de sus características. Para más información acerca de IPFW acceder a su página en el Handbook de FreeBSD o directamente a la página de manual (man ipfw).



Se denomina traffic shaping o packet shaping al proceso de limitar el ancho de banda de ciertas transferencias en una red.

La interfaz dummynet permite controlar el tráfico a través de las interfaces de red de un sistema FreeBSD, aplicando limitaciones de ancho de banda y tamaños de colas, implementando diferentes políticas de despacho y encolado, y emulando retrasos y pérdidas de paquetes. Esta interfaz es implementada en FreeBSD por la utilidad ipfw.

Configuración del kernel

Para comenzar es necesario compilar un kernel personalizado incluyendo las siguientes opciones para habilitar la interfaz dummynet:

# Opciones para DUMMYNET
options IPFIREWALL
options DUMMYNET
options HZ=1000

Conceptos básicos de dummynet

De acuerdo a la página de manual de dummynet, la opción HZ no es requerida, pero es altamente recomendada. Esto se debe a que el mecanismo de traffic shaping se basa en el retraso de paquetes de forma que la velocidad de transferencia permitida no sea excedida. De esta forma, los paquetes son almacenados para ser enviados luego. Este envío es disparado por un timer del kernel, por lo que una frecuencia más alta permite que el tráfico luzca más "suave" (los delays son menores). Si se deja el valor por defecto de 100 MHz, los paquetes serán enviados cada 10 milisegundos, lo que resulta en más ráfagas. Si en cambio se utiliza una frecuencia de 1000 Hz, los paquetes serán enviados cada milisegundos, con lo cual se obtiene un menor retraso en el envío de paquetes.

El traffic shaping ocurre en tres etapas: configuración de pipes, configuración de colas, y desvío de tráfico a través de las colas y/o pipes.

Los pipes son el elemento básico del traffic shaper. Un pipe se utiliza para emular un enlace de red (network link), el cual posee un cierto ancho de banda, delay y tasa de pérdida de paquetes. Las colas son utilizadas para implementar prioridades basadas en peso, y no pueden ser utilizadas sin un pipe. Todas las colas conectadas a un mismo pipe comparten el ancho de banda del mismo en una cierta proporción configurable. El parámetro de configuración más importante de un pipe es su ancho de banda, el cual se establece con el siguiente comando:

# ipfw pipe 1 config bw 100kbit/s

Este comando crea el pipe 1 (si no existe), y le asigna un ancho de banda de 100 kilobits por segundo. Si el pipe ya existe, sólo le cambia el ancho de banda al nuevo valor especificado.

Existen dos parámetros importantes cuando se configura una cola: el número de pipe al cual se conecta, y el peso. El peso debe estar en el rango de 1 a 100 (por defecto es 1).

# ipfw queue 1 config pipe 1 weight 10

Este comando indica a la interfaz dummynet que configure la cola 1 para que utilice el pipe 1, con un peso de 10. El peso permite especificar la proporción de ancho de banda que la cola utilizará. Las colas con mayor peso utilizarán mayor ancho de banda. Por ejemplo, si un pipe de 120 kbit/s ve tráfico activo desde tres colas, cuyos pesos son 3, 2 y 1, el ancho de banda asignado será 60 kbit/s, 40 kbit/s y 20 kbit/s respectivamente. Si el flujo desde la cola con peso 2 desaparece, dejando sólo las colas con pesos 3 y 1, la asignación de ancho de banda será 90 kbit/s y 30 kbit/s respectivamente.

El concepto de peso parece extraño pero es bastante simple. Si dos colas tienen el mismo peso, obtendrán la misma asignación de ancho de banda. Si una cola tiene el doble de peso que otra, recibirá el doble de ancho de banda que la segunda. Las colas sin tráfico no se toman en cuenta cuando se asigna ancho de banda.

Otra opción útil es mask. Tiene la misma sintaxis que las colas y pipes, agregando el identificador "mask" al final de la opción de configuración. El efecto es enmascarar los paquetes y convertir un flujo en varios, utilizando la máscara para establecer a qué flujo pertenece un paquete. La máscara por defecto está vacía, lo cual significa que todos los paquetes caen en un mismo flujo. Utilizando la máscara "all" todos los bits de todos los campos son significativos, esto significa que todas las conexiones TCP y UDP son consideradas flujos. Para el caso de los pipes, cada flujo actúa como un pipe separado (un clon exacto del pipe original con los mismos parámetros). Para las colas, los flujos actúan como varias colas, con el mismo peso que la cola original. Esto significa que es posible utilizar una máscara para compartir de forma equitativa cierto ancho de banda.

Configuración simple

A partir de este punto se asume que el lector va a integrar estas reglas en la configuración del firewall actual, o que se va a utilizar ipfw sólo para traffic shaping, en tal caso se recomienda altamente configurar la opción del kernel IPFIREWALL_DEFAULT_TO_ACCEPT para evitar quedar sin acceso al servidor (algo que sucedería al ejecutar un "flush").

La configuración más simple consiste en limitar el ancho de banda para todo tipo de tráfico, por ejemplo limitarlo a 5000 Kbit/s.

A modo de ejemplo, quiero limitar el ancho de banda en un servidor NFS corriendo FreeBSD, para que no se sature de tráfico NFS y así evitar problemas.

Antes de comenzar con la configuración de traffic shaping en el servidor NFS, hacer una prueba simple para estimar el ancho de banda promedio alcanzable por el protocolo NFS, de acuerdo a las limitaciones de la red desde el cliente hasta el servidor NFS. Para ello, crear en el cliente un archivo de gran tamaño con contenido aleatorio en el sistema de archivos local:

root@hal9000:~ # dd if=/dev/urandom of=/borrar bs=4096 count=100000
100000+0 records in
100000+0 records out
409600000 bytes transferred in 6.286862 secs (65151741 bytes/sec)

En este caso se trata de un cliente FreeBSD, pero puede utilizarse un cliente corriendo GNU/Linux de igual forma.

Inmediatamente forzar una sincronización para que se completen todas las escrituras a disco pendientes:

root@hal9000:~ # sync

La prueba a correr consiste en copiar el archivo "/borrar" a un filesystem NFS montado desde el servidor NFS remoto:

root@hal9000:~ # mount | grep nfs
192.168.1.200:/zdata/webapp/www-data on /mnt/prueba (nfs, nfsv4acls)

Se observa que el sistema de archivos "/zdata/webapp/www-data", alojado en el servidor NFS (cuya dirección IP es 192.168.1.200), se encuentra montado en el cliente bajo "/mnt/prueba". Cambiar a dicho directorio:

root@hal9000:~ # cd /mnt/prueba

Copiar el archivo de gran tamaño al sistema de archivos NFS y verificar la tasa de transferencia (ancho de banda):

root@hal9000:/mnt/prueba # dd if=/borrar of=prueba bs=4096
100000+0 records in
100000+0 records out
409600000 bytes transferred in 7.505724 secs (54571685 bytes/sec)
root@hal9000:/mnt/prueba # dd if=/borrar of=prueba bs=4096
100000+0 records in
100000+0 records out
409600000 bytes transferred in 10.294282 secs (39789079 bytes/sec)
root@hal9000:/mnt/prueba # dd if=/borrar of=prueba bs=4096
100000+0 records in
100000+0 records out
409600000 bytes transferred in 9.859976 secs (41541683 bytes/sec)

En este caso, el ancho de banda promedio para la transferencia ronda aproximadamente 40 megabytes por segundo. Este es el límite alcanzable por la red en las condiciones actuales.

Con este valor es posible comprobar luego si el límite impuesto por el traffic shaper se está cumpliendo o no.

La configuración de shaping más básica consiste en limitar todo el tráfico indiscriminadamente. Para ello se crea un pipe en la configuración de firewall del servidor NFS:

root@fbsd10-nfs:~ # ipfw pipe 1 config bw 5000kbit/s
root@fbsd10-nfs:~ # ipfw pipe list
00001:   5.000 Mbit/s    0 ms burst 0 
q131073  50 sl. 0 flows (1 buckets) sched 65537 weight 0 lmax 0 pri 0 droptail
 sched 65537 type FIFO flags 0x0 0 buckets 0 active

Tal como se ha configurado, este pipe posee un ancho de banda máximo de 5000 Kbit/s (menos de 5 megabytes por segundo).

A continuación se agregan las reglas para que todo el tráfico, tanto entrante como saliente, sea a través de este pipe:

root@fbsd10-nfs:~ # ipfw add 50 pipe 1 ip from any to any in recv em0
00050 pipe 1 ip from any to any in recv em0
root@fbsd10-nfs:~ # ipfw add 51 pipe 1 ip from any to any out xmit em0
00051 pipe 1 ip from any to any out xmit em0

Por supuesto, el número de regla depende de la configuración actual del firewall (en este caso puntual he decidido ubicarlas lo más alto posible).

Notar que la sintaxis para configurar el pipe es extremadamente simple: "ipfw add 50 pipe 1 ip from any to any in recv em0". Esto básicamente significa: agregar la regla número 50, enviar al pipe 1 todos los paquetes del protocolo ip entrantes en la interfaz em0".

Una vez agregadas las reglas es posible verificar su configuración con el siguiente comando:

root@fbsd10-nfs:~ # ipfw list | grep 0005
00050 pipe 1 ip from any to any in recv em0
00051 pipe 1 ip from any to any out xmit em0

Con estas reglas de shaping en funcionamiento es posible repetir la prueba desde el cliente:

root@hal9000:/mnt/prueba # dd if=/borrar of=prueba bs=4096

En el servidor, se observa la conexión TCP establecida desde el cliente:

root@fbsd10-nfs:~ # netstat -4 | grep nfs
tcp4   26064    268 fbsd10-nfs.nfsd  hal9000.752   ESTABLISHED

Y al monitorear el sistema con atop se observa que el límite al ancho de banda está siendo aplicado (la suma de "si" más "so" es menor a 5000 Kbit/s):

En este caso se comparte un mismo pipe para tráfico entrante y saliente. Esto significa que el total de tráfico a través del mismo será menor o igual al límite establecido, sin importar si el tráfico es de entrada o salida. Si se desea implementar un pseudo enlace con ancho de banda simétrico (el mismo ancho de banda tanto para entrada como para salida) será necesario utilizar dos pipes de iguales características, enviando el tráfico entrante hacia uno y el saliente hacia el otro.

Al finalizar la copia, dd reporta una tasa de transferencia promedio de 584453 bytes por segundo, lo que equivale aproximadamente a 4566 Kbit/s:

root@hal9000:/mnt/prueba # dd if=/borrar of=prueba bs=4096
100000+0 records in
100000+0 records out
409600000 bytes transferred in 700.826398 secs (584453 bytes/sec)

El problema con una configuración tan básica, es que se filtra absolutamente todo tráfico IP. Para comprobar esto basta con hacer una prueba rápida con iperf.

En el servidor NFS, elegir un puerto para el servidor iperf y abrir el firewall (por ejemplo el puerto 6000):

root@fbsd10-nfs:~ # ipfw add 630 allow tcp from any to me dst-port 6000 in via em0
00630 allow tcp from any to me dst-port 6000 in via em0

Luego lanzar iperf en modo servidor:

root@fbsd10-nfs:~ # iperf -s -p 6000
------------------------------------------------------------
Server listening on TCP port 6000
TCP window size: 64.0 KByte (default)
------------------------------------------------------------

Desde el cliente, hacer una prueba de transferencia de 100 megabytes:

root@hal9000:/mnt/prueba # iperf -c 192.168.1.200 -p 6000 -l 100M
------------------------------------------------------------
Client connecting to 192.168.1.200, TCP port 6000
TCP window size: 32.5 KByte (default)
------------------------------------------------------------
[  3] local 192.168.50.231 port 51985 connected with 192.168.1.200 port 6000
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-176.9 sec   100 MBytes  4.74 Mbits/sec

Se observa que el tráfico se limita a menos de 5 megabits por segundo.

Configuración avanzada

Con la configuración básica de ejemplo se ha limitado el ancho de banda para todo el tráfico IP. Esto es una medida bastante drástica, si lo que se necesitaba era sólo limitar el tráfico asociado al protocolo NFS. A continuación se detalla una configuración algo más avanzada que permite implementar esta solución.

Antes de comenzar se debe borrar la configuración anterior:

root@fbsd10-nfs:~ # ipfw 00050 delete; ipfw 00051 delete; ipfw pipe 1 delete
root@fbsd10-nfs:~ # ipfw list | grep pipe
root@fbsd10-nfs:~ # ipfw pipe list
root@fbsd10-nfs:~ #

Primero se debe crear un pipe, por ejemplo con un ancho de banda de 10 megabits:

root@fbsd10-nfs:~ # ipfw pipe 1 config bw 10240kbit/s
root@fbsd10-nfs:~ # ipfw pipe list
00001:  10.240 Mbit/s    0 ms burst 0 
q131073  50 sl. 0 flows (1 buckets) sched 65537 weight 0 lmax 0 pri 0 droptail
 sched 65537 type FIFO flags 0x0 0 buckets 0 active

Luego es posible crear una cola asignada al mismo:

root@fbsd10-nfs:~ # ipfw queue 1 config pipe 1 
root@fbsd10-nfs:~ # ipfw queue list
q00001  50 sl. 0 flows (1 buckets) sched 1 weight 1 lmax 0 pri 0 droptail

El puerto utilizado por NFS para transmitir archivos es el 2049:

root@fbsd10-nfs:~ # cat /etc/services | grep nfsd
nfsd-status     1110/tcp   #Cluster status info
nfsd-keepalive  1110/udp   #Client status info
nfsd            2049/sctp  nfs          # NFS server daemon
nfsd            2049/tcp   nfs          # NFS server daemon
nfsd            2049/udp   nfs          # NFS server daemon

Entonces, lo que se desea es filtrar todo el tráfico entrante hacia el puerto 2049, y todo tráfico saliente desde el mismo. Para ello se utilizan las siguientes reglas:

root@fbsd10-nfs:~ # ipfw add 00050 queue 1 tcp from me 2049 to any out
00050 queue 1 tcp from me 2049 to any out
root@fbsd10-nfs:~ # ipfw add 00051 queue 1 tcp from any to me 2049 in
00051 queue 1 tcp from any to me dst-port 2049 in
root@fbsd10-nfs:~ # ipfw list | grep "queue 1"
00050 queue 1 tcp from me 2049 to any out
00051 queue 1 tcp from any to me dst-port 2049 in

Todo el tráfico saliente originado desde el puerto 2049, al igual que todo tráfico entrante hacia el mismo, se asigna a la cola 1.

Desde el cliente NFS, repetir la prueba:

emi@hal9000:/mnt/prueba % dd if=/borrar of=prueba bs=4096
100000+0 records in
100000+0 records out
409600000 bytes transferred in 346.075404 secs (1183557 bytes/sec)

Tal como se observa, el límite de ancho de banda impuesto por el pipe 1 se aplica correctamente (< 10 megabits/segundo).

Luego, repetir la prueba con iperf. Lanzar iperf en modo servidor en el servidor NFS:

root@fbsd10-nfs:~ # iperf -s -p 6000
------------------------------------------------------------
Server listening on TCP port 6000
TCP window size: 64.0 KByte (default)
------------------------------------------------------------

Ejecutar un test desde el cliente:

emi@hal9000:/mnt/prueba % iperf -c 192.168.1.200 -p 6000 -l 10M
------------------------------------------------------------
Client connecting to 192.168.1.200, TCP port 6000
TCP window size: 32.5 KByte (default)
------------------------------------------------------------
[  3] local 192.168.50.231 port 52864 connected with 192.168.1.200 port 6000
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.1 sec  1.09 GBytes   934 Mbits/sec

Se observa que se mide un ancho de banda de 934 megabits/segundo. Perfecto, el límite se impone sólo para NFS, tal como se definió en las reglas del firewall.

Ahora bien, esta cola tal como se ha configurado no tiene utilidad. Es decir, estas reglas podrían haber sido aplicadas directamente sobre el pipe. Sin embargo, la cola permite asignar diferentes prioridades a diferentes tipos de tráfico sobre un mismo pipe. Supongamos por ejemplo que se desea limitar el tráfico total de NFS más iperf (por hacer una prueba) a 10 megabits. Para ello, deben compartir el mismo pipe. Pero a su vez se desea que este ancho de banda se reparta 60/40 entre NFS e iperf, si conviven al mismo tiempo. Aquí es donde las colas son útiles.

Para implementar esta configuración, se utilizará el pipe 1 creado anteriormente (cuyo ancho de banda es de 10 megabits), la cola 1 estará dedicada al tráfico NFS, y se creará una nueva cola para el tráfico de iperf.

Primero es necesario reconfigurar la cola 1 para que su peso sea 60:

root@fbsd10-nfs:~ # ipfw queue 1 config pipe 1 weight 60
root@fbsd10-nfs:~ # ipfw queue list
q00001  50 sl. 0 flows (1 buckets) sched 1 weight 60 lmax 0 pri 0 droptail

Luego se debe crear la cola 2, asignada al mismo pipe, pero con un peso de 40 (para el tráfico de iperf):

root@fbsd10-nfs:~ # ipfw queue 2 config pipe 1 weight 40
root@fbsd10-nfs:~ # ipfw queue list
q00001  50 sl. 0 flows (1 buckets) sched 1 weight 60 lmax 0 pri 0 droptail
q00002  50 sl. 0 flows (1 buckets) sched 1 weight 40 lmax 0 pri 0 droptail

Finalmente, agregar las reglas del firewall para que el tráfico entrante hacia el puerto 6000, y saliente desde el mismo, fluya a través de la cola 2:

root@fbsd10-nfs:~ # ipfw add 00052 queue 2 tcp from me 6000 to any out
00052 queue 2 tcp from me 6000 to any out
root@fbsd10-nfs:~ # ipfw add 00053 queue 2 tcp from any to me 6000 in
00053 queue 2 tcp from any to me dst-port 6000 in
root@fbsd10-nfs:~ # ipfw list | grep 0005
00050 queue 1 tcp from me 2049 to any out
00051 queue 1 tcp from any to me dst-port 2049 in
00052 queue 2 tcp from me 6000 to any out
00053 queue 2 tcp from any to me dst-port 6000 in

Para verificar esta nueva configuración, ahora es necesario lanzar tanto la copia NFS como la prueba con iperf al mismo tiempo.

Ejecutar iperf en el servidor NFS:

root@fbsd10-nfs:~ # iperf -s -p 6000
------------------------------------------------------------
Server listening on TCP port 6000
TCP window size: 64.0 KByte (default)
------------------------------------------------------------

Desde el cliente, lanzar la copia y el test con iperf en simultáneo:

emi@hal9000:/mnt/prueba % iperf -c 192.168.1.200 -p 6000 -l 100M > /dev/null & ; dd if=/borrar of=prueba bs=4096 count=25600
[1] 1690

Al finalizar el test con iperf, se observa un ancho de banda para tráfico hacia/desde el puerto 6000 de casi 4 megabits/segundo:

root@fbsd10-nfs:~ # iperf -s -p 6000
------------------------------------------------------------
Server listening on TCP port 6000
TCP window size: 64.0 KByte (default)
------------------------------------------------------------
[  4] local 192.168.1.200 port 6000 connected with 192.168.50.231 port 17654
[ ID] Interval       Transfer     Bandwidth
[  4]  0.0-103.0 sec  47.8 MBytes  3.90 Mbits/sec

Esto equivale a el 40% del ancho de banda del pipe 1, lo que coincide con el peso 40 asignado a la cola 2.

Luego, al finalizar la copia con dd en el cliente NFS, se observa un ancho de banda de 812279 bytes/segundo:

emi@hal9000:/mnt/fbsd10-nfs % iperf -c 192.168.1.200 -p 6000 -l 100M > /dev/null & ; dd if=/borrar of=prueba bs=4096 count=25600
[1] 1690
25600+0 records in
25600+0 records out
104857600 bytes transferred in 129.090584 secs (812279 bytes/sec)
[1]  + Done                          iperf -c 192.168.1.200 -p 6000 -l 100M > /dev/null

Este valor equivale aproximadamente a 6,19 megabits/segundo, lo que coincide con el peso 60 de la cola 1 asignada al pipe 1 de 10 megabits.

Que el ancho de banda para la copia NFS haya resultado algo mayor a 6 megabits, se debe a que dicha transferencia finalizó después que la prueba con iperf. Recordar que el peso de las colas aplica sólo entre flujos que coinciden en un mismo tiempo. Por ende, si queda un único flujo a través de un pipe, se le asigna todo el ancho de banda del mismo independientemente de que peso tenga la cola correspondiente.

Referencias

FreeBSD Handbook - Network Communication - Firewalls

FreeBSD Handbook - Network Communication - Firewalls - IPFW

man dummynet
man ipfw


Tal vez pueda interesarte


Compartí este artículo