Tenho uma pergunta de 'entendimento', mas ela tem um caso do mundo real que eu simplifiquei. Considere esta rede de exemplo (com IPs simplificados): Da esquerda para a direita:
- Três hosts S1-S3 (IPs, executando três programas R1-R3 respectivamente; Um switch com 3 VLANS e uma porta trunk conectada à eth0 de um quarto host S4. Todos são Ubuntu 20.04 LTS, mas isso provavelmente é irrelevante.
- O S4 está executando o P1 que se vincula a três soquetes UDP, um em cada uma das três interfaces VLAN nas respectivas redes S1,2,3
- P1 Envia mensagens UDP unicast e multicast para várias portas em cada um desses sockets a taxas de 10-100Hz. Há uma
OUTPUT
cadeia netfilter controlando esses fluxos.
Cada uma das áreas vermelhas representa algo que pode estar 'inativo'. Em todos esses casos, exceto um, os datagramas UDP são imediatamente descartados:
- Interface virtual
V10
está com link inativo; processoR2
não está em execução; ouOUTPUT
encadeiaDROP
o fluxo - Em cada caso, os pacotes são descartados silenciosamente por S2 ou um erro é relatado de volta para P1 e o pacote descartado. Todos os outros mcast continuam a fluir. - No caso do S3, após a remoção do
OUTPUT
bloco, otx
buffer do soquetev12
rapidamente se enche com pacotes unicast destinados12.1
e isso bloqueia qualquer gravação futura de unicast e multicast.
Neste estado, um strace
on P1
pode se parecer com isto (aparado):
15:38:27 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = 31674
15:38:27 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = 31674
15:38:27 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = 31674
15:38:27 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = -1 EAGAIN (Resource temporarily unavailable)
15:38:30 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = 31674
15:38:30 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = 31674
15:38:30 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = 31674
15:38:30 sendto(9, ... {sa_family=AF_INET, sin_port=htons(2347), sin_addr=inet_addr("12.1")}, 16) = -1 EAGAIN (Resource temporarily unavailable)
E ss
mostra o problema:
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
UNCONN 0 148992 0.0.0.0:51047 0.0.0.0:* users:(("task",pid=381888,fd=9))
skmem:(r0,rb212992,t148992,tb131070,f0,w0,o0,bl0,d0)
Então temos 4 * ~30kb de gravações no buffer de socket (padrão 120kb) com o 4º reportando EAGAIN
quando o buffer está cheio. O buffer está enchendo repetidamente enquanto o SO está esperando por uma ARP
resposta S3
que nunca virá.
Duas perguntas depois de tudo isso:
- UDP não é confiável por natureza. Nossa aplicação fica muito feliz que pacotes sejam descartados de outra forma, então por que seriam
tx
enfileirados dessa forma enquanto o kernel tenta resolver ARP? (Considere o caso em que o S3 pode estar conectado apenas ocasionalmente, mas outros hostsv12
ainda podem ser alcançáveis.) - O buffer parece ser esvaziado (e então rapidamente preenchido com novas gravações) a cada 3 segundos. Um dos resultados é que qualquer multicast que entra na mesma
tx
fila sai em pequenas rajadas em vez de na taxa de envio constante. Claro que podemos abrir mais soquetes de envio, mas onde esse valor é definido e esse comportamento é ajustávelsysctl
?
Seu aplicativo pode ser, mas a maioria dos outros aplicativos não fica muito feliz quando os pacotes são descartados. Eles geralmente têm timeouts e retransmissões, mas esses geralmente são muito mais longos do que o necessário para uma consulta ARP ter sucesso.
Por exemplo, se o kernel fosse descartar um TCP SYN (em vez de enfileirar) porque ele tem que emitir uma consulta ARP para o gateway, levaria 3 segundos para a retransmissão, contra ~0,003 segundos para a resposta ARP. O mesmo vale para consultas DNS baseadas em UDP e a maioria dos outros protocolos. (Sem mencionar aqueles que não têm retransmissão – Syslog sobre UDP não tem, e perderia a maioria das mensagens se elas não fossem frequentes o suficiente para manter a entrada do cache ARP ativa.)
net.ipv4.neigh.*.unres_qlen_bytes
parece ser o sysctl (assim como o obsoleto.unres_qlen
) que controla a quantidade de dados que podem ser enfileirados para um vizinho não resolvido..mcast_solicit
Os sysctls × adjacentes.retrans_time_ms
controlam quantas vezes (e em quais intervalos) uma solicitação de multicast ou consulta ARP de transmissão será repetida antes que a entrada vizinha entre no estado 'FALHOU' e todos os seus pacotes na fila sejam descartados.(Quando isso acontece, o próximo pacote acionará uma nova série de tentativas de descoberta de vizinhos enquanto novos pacotes são enfileirados novamente, resultando no ciclo de 3 segundos nas configurações padrão.)
Se você estiver tentando encontrar isso nas fontes do Linux, use grep
QUEUE_LEN_BYTES
e procure especificamente por__neigh_event_send()
emnet/core/neighbour.c
. Parece que as filas estão anexadas às mesmas entradas vizinhas que você vê emip [-s] neigh
, embora não mostre o tamanho da fila por vizinho.