Al intentar comprender la gestión de tráfico con tc, inicialmente se pensó en abordar el tema desde la perspectiva del espacio de usuario. Sin embargo, los conceptos de qdisc (cola de distribución) y class (clase) resultaron ser más complejos de lo esperado. El análisis directo del código fuente se presenta como la vía más clara.
Flujo de Creación de Qdisc (HTB)
Al ejecutar el comando tc qdisc add dev tap0 root handle 1: htb default 2, se genera la siguiente pila de llamadas en el kernel:
%stc 0xffffffff81744fe0 : qdisc_create+0x0/0x410 [kernel]
0xffffffff81745597 : tc_modify_qdisc+0x1a7/0x5b0 [kernel]
0xffffffff8172b336 : rtnetlink_rcv_msg+0xe6/0x230 [kernel]
0xffffffff8174ee24 : netlink_rcv_skb+0xa4/0xc0 [kernel]
0xffffffff8172b248 : rtnetlink_rcv+0x28/0x30 [kernel]
0xffffffff8174e5da : netlink_unicast+0x12a/0x1b0 [kernel]
0xffffffff8174eb01 : netlink_sendmsg+0x4a1/0x5f0 [kernel]
0xffffffff816fe098 : sock_sendmsg+0x38/0x50 [kernel]
0xffffffff816feb41 : ___sys_sendmsg+0x281/0x290 [kernel]
0xffffffff816ff491 : __sys_sendmsg+0x51/0x90 [kernel]
0xffffffff816ff4e2 : sys_sendmsg+0x12/0x20 [kernel]
0xffffffff818244f2 : entry_SYSCALL_64_fastpath+0x16/0x71 [kernel]
Flujo de Creación de Clases (HTB)
Cuando se añade una clase tc class, la pila de llamadas observada es:
tc
0xffffffff81741950 : qdisc_create_dflt+0x0/0x80 [kernel]
0xffffffffc0a87abf [sch_htb]
0xffffffff81744d85 : tc_ctl_tclass+0x3b5/0x400 [kernel] (inexact)
0xffffffff8172b336 : rtnetlink_rcv_msg+0xe6/0x230 [kernel] (inexact)
0xffffffff81706bd7 : __alloc_skb+0x87/0x1f0 [kernel] (inexact)
0xffffffff8172b250 : rtnetlink_rcv_msg+0x0/0x230 [kernel] (inexact)
0xffffffff8174ee24 : netlink_rcv_skb+0xa4/0xc0 [kernel] (inexact)
0xffffffff8172b248 : rtnetlink_rcv+0x28/0x30 [kernel] (inexact)
0xffffffff8174e5da : netlink_unicast+0x12a/0x1b0 [kernel] (inexact)
0xffffffff8174eb01 : netlink_sendmsg+0x4a1/0x5f0 [kernel] (inexact)
0xffffffff813912f1 : aa_sock_msg_perm+0x61/0x150 [kernel] (inexact)
0xffffffff816fe098 : sock_sendmsg+0x38/0x50 [kernel] (inexact)
0xffffffff816feb41 : ___sys_sendmsg+0x281/0x290 [kernel] (inexact)
0xffffffff8120018b : mem_cgroup_try_charge+0x6b/0x1b0 [kernel] (inexact)
0xffffffff8119dfd7 : lru_cache_add_active_or_unevictable+0x27/0xa0 [kernel] (inexact)
0xffffffff811bf87a : handle_mm_fault+0xcaa/0x1820 [kernel] (inexact)
0xffffffff816ff491 : __sys_sendmsg+0x51/0x90 [kernel] (inexact)
0xffffffff816ff4e2 : sys_sendmsg+0x12/0x20 [kernel] (inexact)
0xffffffff818244f2 : entry_SYSCALL_64_fastpath+0x16/0x71 [kernel] (inexact)
Se observa que cada vez que se crea una clase, se genera un nuevo qdisc. La pregunta es dónde se almacenan estos qdisc recién creados.
Manejo de Netlink en el Kernel
El subsistema rtnetlink es fundamental para la configuración de redes. Su inicialización:
static int __net_init rtnetlink_net_init(struct net *net)
{
struct sock *sk;
struct netlink_kernel_cfg cfg = {
.groups = RTNLGRP_MAX,
.input = rtnetlink_rcv,
.cb_mutex = &rtnl_mutex,
.flags = NL_CFG_F_NONROOT_RECV,
};
sk = netlink_kernel_create(net, NETLINK_ROUTE, &cfg);
if (!sk)
return -ENOMEM;
net->rtnl = sk;
return 0;
}
Esto indica que NETLINK\_ROUTE se utiliza para el manejo de operaciones relacionadas con la red, incluyendo la configuración de políticas de tráfico.
Operaciones de Clase HTB
La estructura htb\_class\_ops define las operaciones específicas para la gestión de clases en el algoritmo HTB:
static const struct Qdisc_class_ops <strong>htb_class_ops</strong> = {
.graft = htb_graft,
.leaf = htb_leaf,
.qlen_notify = htb_qlen_notify,
.get = htb_get,
.put = htb_put,
.change = htb_change_class,
.delete = htb_delete,
.walk = htb_walk,
.tcf_chain = htb_find_tcf,
.bind_tcf = htb_bind_filter,
.unbind_tcf = htb_unbind_filter,
.dump = htb_dump_class,
.dump_stats = htb_dump_class_stats,
};
static struct Qdisc_ops <strong>htb_qdisc_ops</strong> __read_mostly = {
.cl_ops = &htb_class_ops,
.id = "htb",
.priv_size = sizeof(struct htb_sched),
.enqueue = htb_enqueue,
.dequeue = htb_dequeue,
.peek = qdisc_peek_dequeued,
.init = htb_init,
.reset = htb_reset,
.destroy = htb_destroy,
.dump = htb_dump,
.owner = THIS_MODULE,
};
La función tc\_ctl\_tclass es la encargada de procesar las operaciones sobre las clases.
Estructura Jerárquica y Clasificación de Paquetes
La gestión de la estructura de árbol en HTB y cómo se asocian las sub-colas a las colas padre es crucial. La función htb\_classify es la responsable de dirigir un paquete (sk\_buff) a la clase correspondiente dentro de un qdisc:
static struct htb_class *htb_classify(struct sk_buff *skb, struct Qdisc *sch,
El proceso de clasificación típicamente utiliza el campo skb->priority. Una pregunta importante surge sobre por qué no se realiza el filtrado (filter) antes de determinar la clase, dado que el priority ya está establecido en el paquete. El mecanismo tc filter es el que se encarga de asignar este valor de prioridad.
La relación entre qdisc, class y filter se puede entender de la siguiente manera: un qdisc puede contener una o más class. Cada class puede tener asociados filtros (filter) que dirigen el tráfico a otros qdisc (que a su vez son hojas o nodos intermedios en la jerarquía). Cada qdisc (especailmente los que no son raíz) está asociado a una class que define sus características de tráfico (tasa, techo, etc.).
Si los filtros se aplicaran directamente sobre el qdisc raíz, la compartición de ancho de banda entre clases hijas sería más limitada. Sin embargo, la creación de class permite una gestión más granular.
Asignación de Prioridad a Paquetes
El valor skb->priority se establece durante la operación htb\_enqueue. Los valores predefinidos para esta prioridad son:
#define TC_PRIO_BESTEFFORT 0
#define TC_PRIO_FILLER 1
#define TC_PRIO_BULK 2
#define TC_PRIO_INTERACTIVE_BULK 4
#define TC_PRIO_INTERACTIVE 6
#define TC_PRIO_CONTROL 7
Es importante notar que tc filter puede asignar estos valores de prioridad al skb, incluso si el paquete original no los lleva explícitamente.
Ejemplo de Configuración
Un ejemplo típico de configuración con HTB es el siguiente:
# Configuración de la cola raíz HTB en eth0
tc qdisc add dev eth0 root handle 1: htb default 21
# Añadir clase principal con 20Mbit de tasa y techo
tc class add dev eth0 parent 1: classid 1:1 htb rate 20mbit ceil 20mbit
# Añadir otra clase principal con 80Mbit de tasa y techo
tc class add dev eth0 parent 1: classid 1:2 htb rate 80mbit ceil 80mbit
# Añadir una sub-clase con tasa de 40Mbit y techo de 20Mbit
tc class add dev eth0 parent 1: classid 1:21 htb rate 40mbit ceil 20mbit
# Añadir otra sub-clase con tasa de 40Mbit y techo de 80Mbit
tc class add dev eth0 parent 1:2 classid 1:22 htb rate 40mbit ceil 80mbit
# Filtrar tráfico del puerto 80 para la clase 1:21
tc filter add dev eth0 protocol ip prio 1 u32 match ip dport 80 0xffff flowid 1:21
# Filtrar tráfico del puerto 25 para la clase 1:22
tc filter add dev eth0 protocol ip prio 1 u32 match ip dport 25 0xffff flowid 1:22
# Filtrar tráfico del puerto 23 para la clase 1:1
tc filter add dev eth0 protocol ip prio 1 u32 match ip dport 23 0xffff flowid 1:1
El proceso de htb\_enqueue es donde se implementa la lógica de asignación de prioridades a los paquetes.