sexta-feira, 1 de fevereiro de 2008

Controle de Tráfego

Controle de tráfego é o nome dado ao conjunto de sistemas de enfileiramento (queuing) e mecanismos pelos quais os pacotes são recebidos e transmitidos em um roteador. Isso inclui que pacotes aceitar a que velocidade e em qual interface e que pacotes devem ser priorizados. O termo QoS (Quality of Service) é frequentemente utilizado como sinônimo de Controle de Tráfego.

O controle de tráfego no Linux se encontra em um estado maduro e bastante avançado em termos de recursos.

Conceitos
Para entendermos melhor o controle de tráfego, vamos nos familiarizar com alguns conceitos utilizados nele:

Filas (queues)
Uma fila é um local (ou buffer) contendo um número finito de itens aguardando por uma ação ou serviço. Em redes, uma fila é local onde os pacotes aguardam antes de serem enviados pelo hardware.

Shapping
Shapping é o ato de "atrasar" o envio de pacotes de forma que o tráfego fique dentro de um determinado limite de velocidade. Como efeito colateral um shapper pode deixar um determinado tráfego mais regular, ao remover ou "achatar" as rajadas de pacotes.

Scheduling
Scheduling ou escalonamento é o ato de ordenar ou re-ordenar os pacotes para serem enviados. O método mais comum de escalonamento é o FIFO (First-in First-out, "o primeiro a chegar é o primeiro a sair"). Outros métodos de escalonamento incluem o SFQ, que procura dar a cada "flow" uma chance de transmitir seus pacotes, ou o RED, que procura descartar pacotes de forma randômica ao se atingir uma determinada condição de forma a evitar que o backbone fique saturado.

Classifying
Classificação (classifying) é o mecanismo pelo qual os pacotes são separados de forma a terem tratamentos diferentes, possivelmente colocando-os em diferentes filas de saída. Uma pacote ou flow pode ser classificado de várias maneiras diferentes ao atravessar um roteador ou rede.

Policing
Policing é o mecanismo pelo qual o tráfego pode ser limitado. Assim é possível garantir de que um determinado tipo de tráfego não consuma mais banda do que foi destinado a ele.

Componentes do Controle de Tráfego do Linux

O Linux possui os seguintes componentes: qdisc, class e filters. Antes de vermos com mais detalhes cada um deles, vamos fazer uma rápida relação desses elementos com os conceitos expostos:

ConceitoComponente
ShappingAs "class" oferecem a capacidade de "shapping"
Schedulingum qdisc é um escalonador. Ele pode ser simples como um FIFO (fila) ou pode conter classes dentro dele
ClassifyingA classificação é feita dentro de um "filter" através de um "classificador"
PolicingO policing é implementado no Linux como uma parte de um filter


Qdisc
Os qdiscs (abreviação de Queueing Disciplines) são as filas de saída dos pacotes. Um qdisc exerce a função de escalonador de pacotes também.

O Linux possui dois tipos de qdisc: classless e classfull qdiscs. Os qdics classless não podem conter classes definidas pelo usuário, embora alguns deles possuam mais de uma fila de saída. Já os qdiscs classfull podem conter subclasses definidas pela usuário, permitindo assim a separação e a atribuição de quantidades diferentes de banda para cada tipo de fluxo.

Cada interface de rede possui um qdisc "root". O padrão do Linux é atribuir um qdisc do tipo "pfifo_fast" para o qdisc root, mas obviamente, isso pode ser mudado.

Como não pretendemos aqui fazer um "tratado" sobre QoS? no Linux veremos apenas alguns qdiscs, entre eles três classless, pfifo_fast, sfq e tbf e um classfull, o htb. Embora um dos qdiscs classfull mais populares seja o cbq (talvez por ser um dos mais antigos), não trataremos dele, pois é mais complexo e pode ser substituído com vantagens pelo htb.

Classless Qdiscs
PFIFO_FAST
O pfifo_fast é o qdisc padrão do Linux. Ele é baseado no FIFO, mas possui internamente três filas, onde determinado tipo de tráfego pode ser priorizado. Os pacotes são colocados nestas filas de acordo com o seu TOS, assim pacotes pertencentes a tráfegos do tipo interativo recebem maior prioridade e são sempre servidos primeiro. Pacotes na fila 0, são servidos primeiro, e somente quando essa fila se encontra vazia, são servidos os pacotes da fila 1. De maneira análoga são servidos os pacotes da fila 2.

SFQ
O qdisc SFQ (Stochastic Fair Queuing) é um qdisc que procura distribuir de maneira igual a oportunidade para cada flow ser servido. O SFQ mantém internamente várias filas FIFO e utiliza uma função HASH para distribuir os pacotes entre essas filas. As filas são então servidas usando uma função "round-robin". Para evitar que a função hash escolhida acabe privilegiando ou prejudicando um determinado tipo de fluxo, o SFQ possui o parâmetro "perturb" que indica de quanto em quantos segundos a função HASH deve ser recalculada.

TBF
O TBF (Token Bucket Filter) utiliza o modelo de balde de fichas para "shapear" o tráfego. Normalmente o TBF é utilizado quando desejamos simplesmente limitar a velocidade de uma determinada interface. Os principais parâmetros do TBF são

Parâmetrosignificado
limitQuantidade de bytes que podem aguardar na fila
bursttamanho do "balde" em bytes. Esse parâmetro pode ser utilizado para permitir rajadas
ratevelocidade com que os "tokens" chegam ao balde.


Classful Qdiscs

HTB
O HTB (Hierarchical Token Bucket) utiliza o conceito de balde de fichas combinado com um sistema de classes e filtros, que permite a configuração de um controle do tráfego bastante preciso e complexo.

Como o HTB é um qdisc com suporte a classes, ele pode ser utilizado como um qdisc (um escalonador/shaper) ou como uma classe. Quando utilizado como classe o htb possui apenas um parâmetro (opcional) "default" que define qual subclasse deverá ser utilizada caso um pacote não seja classificado por nenhum filtro.

Como um escalanador o HTB suporta, dentre outros, os seguintes parâmetros:

ParâmetroSignificado
ratevelocidade de transmissão dos pacotes
bursttamanho máximo de bytes que pode ser acumulado para rajadas
ceilvelocidade total da classe superior. Esse parâmetro é utilizado para permitir que uma classe tome "emprestado" banda disponível de outra classe


Class
As classes só existem dentro de qdiscs classful e são uma forma de se dividir o tráfego para um tratamento diferenciado. Uma classe não manipula um pacote diretamente, isso é feito pelo qdisc associado à ela. Além da classe "root", existem dois tipos de classes. As classes "leafs", que são classes que não possuem "filhos" e classes "inner" que são classes que possuem "filhos".

Filter
Os filtros exercem a função de separar o tráfego em classes dentro de um qdisc classful. Isso é feito com o auxílio de um "classificador". Além disso um filtro pode exercer a função de "policer" tomando uma ação sempre de acordo com os limites estabelecidos para o fluxo que ele está classificando. Normalmente um filtro tem como política descatar pacotes que excedam o limite estabelecido para o qdisc no qual ele está embutido, no entanto é possível configurar outras ações, como por exemplo, re-classificar o pacote utilizando outro filtro.

Classificadores
Dentro de um filtro, um classificador é utilizado para identificar certos padrões e/ou características dos pacotes e fluxos permitindo assim a separação em classes. Veremos a seguir apenas dois classificadores do Linux:

U32
O U32 é o principal classificador do Linux, pois ele permite a identificação de qualquer parte de um pacote IP (normalmente campos do cabeçalho). O U32 é bastante utilizado quando precisamos identificar IPs? de origem e/ou destino, portas de origem/destino ou mesmo protocolo (icmp, tcp, udp, etc). Veremos a seguir alguns dos principais parâmetros do U32:

ParâmetroSignificado
ip src IP/MASKCasa com pacotes com origem em IP/MASK
ip dst src IP/MASKCasa com pacotes com destino IP/MASK
ip sport NN 0xffffCasa com pacotes com porta de origem NN. O parametro 0xffff é uma máscara de 32 bits
ip dport NN 0xffffCasa com pacotes com porta de destino NN. O parametro 0xffff é uma máscara de 32 bits
ip protocol NN 0xffCasa com pacotes do tipo procolo NN. Olhe em /etc/protocols
ip tols 0xNN 0xMMCasa com os bits NN do campo TOS. Utilize a mascara MM para escolher quais bits se deseja comparar
flowid X:Y os pacotes que casam com essa regra devem ser colocados na class X:Y


O U32 é bastante poderoso e pode procurar por padrões dentro de qualquer lugar do cabeçalho ou mesmo do pacote inteiro. Para isso ele usa os seletores u32, u16, e u8 que casam com padrões de 32, 16 e 8 bits respectivamente. Esses seletores aceitam como parâmetros um padrão de bytes, uma máscara, um deslocamento em relação ao início do cabeçalho ou um deslocamento em relação ao próximo cabeçalho (cabeçalho da camada de transporte por exemplo). Mas vamos deixar essas opções mais avançadas para uma outra oportunidade.

FW
O classificador FW procura por "marcas" feitas por regras do netfilter. Sua utilização é bastante simples pois ele requer apenas dois parâmetros: handle N, informando qual "marca" ele deve procurar e flowid X:Y indicando em qual classe ele deverá colocar os pacotes encontrados.

Configurando o Controle de Tráfego

A configuração do controle de tráfego no Linux é toda feita através de um único comando: o "tc". O comando tc faz parte do pacote iproute2 - o mesmo do comando ip. O funcionamento do comando tc é similar ao do comando ip, e ele tem a forma geral:

# tc opções OBJETO PARÂMETROS

Os objetos do comando tc são: qdisc, class e filter. Veremos a seguir como manipular esses objetos.

NOTA: o tc utiliza as seguintes regras para a especificação de velocidades/banda:

mbps = 1024 kbps = 1024 * 1024 bps => byte/s mbit = 1024 kbit => kilobit/s. mb = 1024 kb = 1024 * 1024 b => byte mbit = 1024 kbit => kilobit

tc qdisc
Utilizamos o objeto qdisc para adicionar, remover e listar qdiscs. Veremos como executar cada uma dessas operções:

Adicionando e removendo qdiscs
Para adicionar um qdisc precisamos indicar em que interface estamos adicionando, qual é "handle" (identificador) do qdisc, quem é o "pai" do qdisc, e finalmente o tipo de qdisc e suas opções. O pai de um qdisc pode ser "root" caso estejamos adicionando um qdisc root ou "parent X:Y" onde X:Y é a classe a qual esse qdisc está sendo adicionado. Exemplos:

Adicionando um qdisc root:

# tc qdisc add dev eth0 root handle 1: tbf rate 5mbit burst 100kbit limit 5k

Adicionando um qdisc a uma classe:

# tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10

Adicionando um qdisc classful e definindo uma classe "padrão":

# tc qdisc add dev eth0 root handle 1: htb default 10

Para remover um qdisc basta utilizar a opção "del" em vez de add.

Listando qdiscs
Para lista um qdisc basta utilizar a opção show, não esquecendo de informar o dispositivo de rede, exemplo:

# tc qdisc show dev eth0

tc class
Como vimos anteriormente as classes funcionam como uma espécie de "subdivisão" de um qdisc classful, permitindo a separação do tráfego em outras classes/qdiscs. No caso específico do qdisc classful que veremos, o htb, é nas classes "leafs" que é feita o shaping.

Adicionando e removendo classes
Para criar uma classe precisamos informar a interface, o qdisc/classe pai dessa classe, o identificador de classe, o tipo de classe e suas opções. Exemplos:

Adicionando uma classe a um qdisc root:

# tc class add dev eth0 root classid 1:0 htb rate 2mbit

Adicionando uma classe como filha de outra classe:

# tc class add dev eth0 parent 1:0 classid 1:100 htb rate 100kbit

Vale lembrar que uma classe "leaf" pode ter um qdisc anexada a ela, mas um classe "inner" não.

Listando classes
Para listar as classes, basta usar o comando com a opção show. Adicionalmente podemos utilizar as opções -s para obtermos a quantidade de bytes e pacotes enviados por cada classe. Exemplo:

# tc -s class show dev eth0

tc filter
Os filtros devem ser "anexados" às classes para efetuarem a separação do tráfego. Um ponto importante a lembrar é a de que as classes possuem os filtros e não o contrário, ou seja, somente quando os pacotes "entram" em uma classe os filtros associadas à ela serão executados. Quando temos uma configuração com classes é importante definir uma classe "raiz" (uma classe no qdisc raiz com o "minor" 0, ex: 1:0), pois a partir dela poderemos criar filtros que apontem para outras classes.

Adicionando e removendo filtros
Para adicionarmos um filtro utilizamos o comando "tc filter add" e informamos à interface, a classe na qual queremos inserir o filtro, o protocolo, o classificador a ser utilizado e suas opções. Exemplos:

Insere um filtro na classe "raiz", casando com pacotes que venham da rede 10.0.0.0/24:

# tc filter add dev eth0 parent 1:0 protocol ip u32 match ip src 10.0.0.0/24 flowid 1:10

Insere um filtro na classe 1:10, casando com pacotes que tenham como destino a porta 80 da máquina 192.168.0.1:

# tc filter add dev eth0 parent 1:10 protocol ip u32 match ip dst 192.168.0.1/32 match ip dport 80 0xffff flowid 1:100

Os filtros aceitam também o parametro "prio N" que define a prioridade com que os filtros de uma mesma classe devem ser utilizados. Exemplo:

Insere um filtro na classe 1:20 com prioriade 2. Esse filtro casa com pacotes que foram marcados pelo netfilter com a tag "6"

# tc filter add dev eth0 parent 1:20 protocol ip prio 2 handle 6 fw flowid 1:200

Para remover um filtro utilize o mesmo comando usado para criá-lo mas com a opção "del" em vez de "add".

Listando filtros
De maneira análoga aos outros objetos, para listar os filtros utilize o comando "tc filter show", como no exemplo:

# tc filter show dev eth0

Mais Exemplos

Um recurso bastante interessante do controle de tráfego no Linux, e em particular do qdisc htb é a possibilidade de se criar classes "irmãs" que podem tomar "emprestado" banda de outra classe, caso essa banda esteja disponível. Vamos criar um exemplo prático para ver como isso funciona.

Vamos supor que temos um link de 5mbits e queremos destinar 2mbits para o tráfego web, mais 2mbits para ftp e 1mbit para o "resto" , no entanto queremos que caso haja banda disponível, o tráfego web possa chegar a 4mbits e o tráfego ftp a 3 mbits. Uma possível solução para esse cenário:

# tc qdisc add dev eth0 root handle 1: htb default 30
# tc class add dev eth0 root classid 1:0 htb rate 5 mbits
# tc class add dev eth0 parent 1:0 classid 1:10 htb rate 2mbit ceil 4mbit
# tc class add dev eth0 parent 1:0 classid 1:20 htb rate 2mbit ceil 3mbit
# tc class add dev eth0 parent 1:0 classid 1:30 htb rate 1mbit

Isso define as classes e suas velocidades. O ponto principal aqui é o parametro "ceil" que define um valor máximo de banda que pode ser utilizado caso haja banda disponível para se pegar "emprestado". Basta agora criar dois filtros para separar o tráfego web e ftp. Pacotes que não pertençam a essas classes caem no padrão definido na primeira regra.

# tc filter add dev eth0 parent 1:0 protocol ip u32 match ip dport 80 0xffff flowid 1:10
# tc filter add dev eth0 parent 1:0 protocol ip u32 match ip dport 20 0xffff flowid 1:20
# tc filter add dev eth0 parent 1:0 protocol ip u32 match ip dport 21 0xffff flowid 1:20

Obs: utilizamos as portas 20 e 21 para caracterizar o tráfego ftp.

Caso não haja tráfego algum para a classe 30, esses 1mbits podem ser divididos pelas classes 1:10 e 1:20.

Nenhum comentário: