Estou executando o CentOS 7 no meu VPS e gostaria de limitar a largura de banda em uma porta específica. Eu olhei em volta extensivamente e, entre as soluções que posso encontrar, é um limite colocado em uma interface ou é uma configuração iptable vagamente descrita que parece ter sido tentada apenas no CentOS 6.
No meu caso, meu lado do servidor Shadowsocks (um aplicativo proxy) está escutando na porta 1080
, 1081
e 1082
no eth0
. Eu gostaria de permitir 1080
largura de banda ilimitada, mas limitar ambos 1081
e 1082
cerca de 1 MBps. Como é um aplicativo proxy, o tráfego de entrada e saída é aproximadamente igual. Observe que é uma única instância do Shadowsocks ouvindo em 3 portas, NÃO 3 instâncias ouvindo em 1 porta cada, portanto, limitar a largura de banda por processo não é aplicável.
Mas, caso contrário, qualquer solução está na mesa para mim, seja algo que o CentOS suporta imediatamente ou algum tipo de camada de monitoramento intermediária. Desde que faça o trabalho, estou aberto a isso.
Desde já, obrigado.
O tráfego pode ser limitado usando apenas o Controle de Tráfego do Linux .
Apenas para esclarecer, shadowsocks cria um túnel com um lado como um proxy SOCKS5 (
sslocal
, estou assumindo que é o que está sendo executado no servidor do OP considerando as portas fornecidas), comunicando-se com um endpoint remoto (ssserver
) que se comunicará com o destino real servidores. shadowsocks manipula SOCKS5 UDP ASSOCIATE e usa então (SOCKS5) UDP na mesma porta que a porta TCP (SOCKS5).Esta solução funciona como está (veja a nota 1) para TCP e UDP, exceto que o UDP pode oferecer desafios adicionais: se uma fonte estiver criando pacotes UDP de tamanho "maior que MTU" (o que provavelmente não deve ser feito por um cliente ou servidor), eles ficam fragmentados. tc , que funciona antes do netfilter na entrada e depois do netfilter na saída , verá os fragmentos. A porta UDP não está disponível em fragmentos, portanto, nenhum filtro poderá capturá-los e quase nenhuma limitação acontecerá. O TCP naturalmente usando MTU para limite de tamanho de pacote (e fazendo a descoberta de MTU de caminho de qualquer maneira) não sofre esse problema na maioria das configurações.
Aqui está uma imagem ASCII de fluxo de pacotes (a imagem inteira normalmente representaria uma atividade de cliente resultando em dois fluxos, um à esquerda e outro à direita do proxy):
Não há necessidade ou uso de se preocupar com o tráfego com o servidor remoto:
De qualquer forma, ficaria muito mais complexo, provavelmente envolvendo mudanças dentro do shadowsocks, para vincular o tráfego do lado remoto/servidor ao lado do cliente para uso do tc .
Para clientes SOCKS5 apenas enviando dados, é necessário limitar a entrada deles para limitar a largura de banda, e para clientes SOCKS5 apenas recebendo dados, é necessário limitar a saída para limitar a largura de banda: a menos que o aplicativo em uso seja bem conhecido, ambas as formas devem ser controladas por tráfego .
O controle de tráfego é um tema complexo, que mal consigo arranhar. Vou dar dois tipos de respostas: a simples e grosseira fazendo policiamento (soltar excesso) apenas, e uma mais complexa, fazendo modelagem (incluindo atrasar antes de ter que largar), com uma interface IFB para contornar as limitações de entrada .
A documentação abaixo deve ser lida para entender os conceitos e a implementação do Linux:
http://www.tldp.org/HOWTO/Traffic-Control-HOWTO/
Além disso, este comando implementado no script de shell (e usando mecanismos semelhantes como nesta resposta) também pode fazer maravilhas:
https://github.com/magnific0/wondershaper
Simples e bruto
Uma ação de polícia é usada para descartar qualquer excesso de portas de correspondência de pacotes (que é um método bruto). Geralmente é usado na entrada , mas também funciona na saída . O tráfego é limitado por taxa, mas pode haver flutuações e compartilhamento injusto entre vários clientes com taxa limitada (especialmente se UDP vs TCP estiver envolvido).
saída (pacotes de saída)
A qdisc mais simples que permite anexar filtros é a prio qdisc , cujos recursos específicos não serão realmente usados.
Basta adicionar o seguinte filtro (com 8 mbits/s <=> 1 MBytes/s) um por porta (
u16 at 0 layer transport
significa "porta de origem"), para TCP e UDP (veja também a nota 2) :Caso eu tenha entendido errado e deva haver apenas um limite comum para 1081 e 1082, use isso em vez dos dois acima, agrupando-os na mesma ação (o que é fácil com o filtro básico / ematch ), que então os tratará em um bucket de token único:
ingresso (pacotes de entrada)
A entrada é mais limitada que a saída (não é possível fazer modelagem ), mas não foi feita no caso simples de qualquer maneira. Usá-lo requer apenas adicionar um
ingress
qdisc (veja a nota 3) :Os filtros equivalentes (
u16 at 2 layer transport
significa "porta de destino"):ou para um único limite, em vez dos dois acima:
Limpeza tc
egress , ingress ou ambas as configurações podem ser substituídas por sua versão aprimorada abaixo. configurações anteriores devem ser limpas primeiro.
Para remover as configurações de tc aplicadas anteriormente, basta excluir a raiz e a entrada qdiscs . Tudo abaixo deles, incluindo filtros, também será removido. A interface padrão qdisc raiz com o handle reservado 0: será colocada de volta.
Configuração mais complexa com qdiscs classful e interface IFB
O uso de modelagem , que pode atrasar os pacotes antes de descartá-los, deve melhorar os resultados gerais. Hierarchy Token Bucket ( HTB ), um qdisc classful lidará com a largura de banda, enquanto abaixo dele o Stochastic Fairness Queuing ( SFQ ) melhorará a justiça entre os clientes quando estiverem competindo dentro da largura de banda restrita.
saída
Aqui está uma imagem ascii descrevendo as próximas configurações:
As larguras de banda limitadas não emprestarão tráfego extra disponível (não foi solicitado pelo OP): é por isso que elas não são uma subclasse de uma classe padrão de "toda a largura de banda disponível". O tráfego padrão restante, incluindo a porta 1080, permanece em 1:, sem tratamento especial. Em diferentes configurações em que as classes têm permissão para emprestar largura de banda disponível, essas classes devem ser colocadas abaixo de uma classe pai com sua taxa definida com um valor preciso da largura de banda máxima disponível, para saber o que emprestar. Portanto, a configuração exigiria um ajuste fino para cada caso. Eu mantive simples.
O qdisc classful htb:
As classes htb, sfq anexado e filtros direcionando para eles:
ou para um único limite, em vez dos 6 comandos acima:
entrada
O qdisc de ingresso não pode ser usado para modelagem (por exemplo, atraso de pacotes), mas apenas para que sejam descartados com filtros como no caso simples. Para ter um melhor controle, um truque está disponível: o Bloco Funcional Intermediário , que aparece como uma interface de saída artificial onde o tráfego de entrada pode ser redirecionado com filtros, mas tem pouca interação com o restante da pilha da rede. Uma vez instalados, os recursos de saída podem ser aplicados nele, mesmo que alguns deles nem sempre sejam úteis, considerando que o controle real do tráfego de entrada não está nas mãos do sistema receptor. Então aqui eu configurei a
ifb0
interface e dupliquei acima ( egress) nele, para ter um tipo de modelagem de entrada se comportando melhor do que apenas policiamento.Criando ifb0 (veja a nota 4) e aplicando as mesmas configurações da saída anterior :
Classes e filtros direcionados a eles:
ou para um único limite, em vez disso, se os 6 comandos acima:
O redirecionamento da entrada de eth0 para a saída de ifb0 é feito abaixo. Para otimizar, redirecione apenas as portas pretendidas em vez de todo o tráfego. A filtragem e a modelagem reais são feitas acima em ifb0 de qualquer maneira.
Notas:
1. Testado usando alguns namespaces de rede no Debian 10 / kernel 5.3. A sintaxe de comandos também foi testada no contêiner / kernel 5.3 do CentOS 7.6 (em vez de 3.10) .
2.
u32 match ip sport 1081 0xffff
poderia ter sido usado para corresponder à porta de origem 1081. Mas não lidaria com a presença de uma opção de IP.u32 match tcp src 1081 0xffff
poderia lidar com isso, mas na verdade requer o uso complexo de três filtros u32 , conforme explicado na página do manual . Então eu escolhibasic match
no final.3.
ingress
tem o identificador reservadoffff:
especificado ou não (o valor do identificador especificado é ignorado), então prefiro não especificá-lo. A referência de entrada porparent ffff:
pode ser substituída por apenasingress
isso é o que eu escolhi.4. Ao criar uma interface IFB pela primeira vez, o módulo ifb é carregado, que por padrão cria automaticamente as interfaces ifb0 e ifb1 no namespace inicial, resultando em erro se o nome da interface ifb0 for solicitado, enquanto na verdade foi criado como resultado do comando. Ao mesmo tempo, esta interface não aparece em um namespace de rede (por exemplo: container) se simplesmente carregar o módulo, então ainda é necessário lá. Portanto, adicionar
2>/dev/null || :
resolve para ambos os casos. Claro que estou assumindo que o suporte do IFB está realmente disponível.