Proxy é qualquer intermediário que fica entre o cliente e o servidor e fala com cada um em nome do outro. O ponto mais baixo em que isso ainda é útil é a camada 4, a de transporte, onde a unidade de trabalho é uma conexão TCP, e não uma requisição HTTP ou um registro TLS. O proxy TCP é o full proxy mais simples que existe, e entender exatamente o que ele consegue e o que não consegue ver ajuda a entender qualquer proxy das camadas de cima.
Origens: o load balancer que não sabia ler
O proxy de camada 4 existe porque os primeiros load balancers (balanceadores de carga) tinham de funcionar sem entender nada do tráfego. Nos anos 1990, um equipamento na frente de um parque de servidores tinha uma única tarefa, distribuir conexões, e o tráfego acima do transporte era protocolo dos outros. Ler esse tráfego significaria implementar o protocolo, e implementar o protocolo significaria quebrar toda vez que ele mudasse.
Por isso o projeto se limitou ao mínimo possível: aceitar uma conexão, escolher um membro do pool, abrir uma segunda conexão e passar os bytes de uma para a outra sem interpretar nada. Essa decisão de não olhar é a arquitetura inteira, e é por isso que o mesmo equipamento atende um protocolo que nem existia quando ele foi fabricado.
O trade-off continua o mesmo até hoje. A camada 4 é rápida, não depende de protocolo e guarda pouco estado; o que ela não consegue é tomar uma decisão que dependa do significado dos bytes.
Duas conexões, emendadas
O que define um proxy TCP é que ele não encaminha pacotes: ele termina uma conexão e abre outra. O cliente faz um handshake TCP completo (SYN, SYN-ACK, ACK) com o proxy e acredita que está falando com o servidor. O proxy, por sua vez, faz o seu próprio handshake, separado, com o servidor real. Daí em diante, o proxy repassa os bytes entre os dois: lê de um socket e escreve no outro, nos dois sentidos, até que um dos lados feche a conexão. Isso se chama connection splicing (a emenda de duas conexões) ou full proxy de camada 4.
Como são duas conexões independentes, elas podem ser diferentes em quase todos os detalhes de transporte. A conexão do lado do cliente e a do lado do servidor têm, cada uma, os seus números de sequência, o seu tamanho de janela, as suas opções TCP e o seu estado de controle de congestionamento. Um link de cliente lento e com perda e um link de servidor rápido deixam de dividir um único loop de controle de ponta a ponta: o proxy faz buffer entre os dois e deixa cada lado andar no seu ritmo. Esse desacoplamento explica boa parte do ganho de desempenho e de resiliência de um full proxy, e nenhum dos dois endpoints percebe nada.
O que a camada 4 vê e o que ela não vê
Um proxy TCP trabalha em cima do header (cabeçalho) de transporte: endereços IP de origem e de destino, portas de origem e de destino, e as flags e opções do TCP. Isso basta para tomar uma decisão de encaminhamento, para balancear a carga entre um pool de servidores, para impor limites de conexão e para aplicar tradução de endereços de origem e de destino. Não basta para enxergar nada da aplicação. Métodos HTTP, URLs, headers, hostnames e qualquer detalhe do handshake TLS ficam no payload (a carga útil), acima do header de transporte, e um proxy puramente de camada 4 trata esse payload como um fluxo opaco de bytes, que ele copia sem fazer parsing.
Esse é o trade-off central. Um proxy de camada 4 é rápido e não depende de protocolo justamente porque não faz parsing do payload: ele faz proxy de HTTP, de TLS, de (Secure Shell), de um protocolo de banco de dados ou de qualquer outra coisa sem reclamar, porque não se importa com o que os bytes significam. Em compensação, ele não consegue tomar decisões que dependam do conteúdo da aplicação, não consegue rotear por URL e não consegue inspecionar um fluxo criptografado. Quando você precisa disso, sobe para um proxy de camada 7 (veja o artigo sobre proxies HTTP) e paga pela visibilidade extra com o custo do parsing.
Há uma nuance importante no tráfego criptografado. Um proxy de camada 4 não consegue ler uma sessão TLS, mas os primeiros bytes do ClientHello do TLS vão em texto claro, e entre eles está o Server Name Indication (). Um proxy de camada 4 pode dar uma olhada nesse único campo para mandar a conexão ao backend certo pelo hostname, sem descriptografar nada; é um padrão comum em TLS passthrough. Isso continua não sendo descriptografia: é ler uma etiqueta em texto claro num fluxo que, fora isso, é opaco.
O endereço de origem que desaparece
Como o proxy abre uma conexão nova até o servidor, o servidor vê como origem o endereço do proxy, e não o do cliente. Para o servidor, todas as conexões vêm de um punhado de IPs do proxy. Em geral isso é proposital (é assim que funcionam o source e o connection pooling), mas apaga justamente a informação que muitos servidores mais querem: quem era o cliente de verdade. Os logs de acesso mostram o proxy; as regras de acesso por IP veem o proxy; a geolocalização vê o proxy.
Dois mecanismos trazem de volta a identidade do cliente. Na camada 7, os proxies HTTP acrescentam um header X-Forwarded-For, mas isso só funciona para HTTP, e só depois que o payload passa pelo parsing. Na camada 4, a resposta é o Proxy Protocol: um header pequeno que o proxy coloca no começo do fluxo TCP do lado do servidor, antes de qualquer dado da aplicação, com o endereço e a porta originais de origem e de destino. O servidor (se entender o Proxy Protocol) lê esse header primeiro, registra o endereço real do cliente e depois trata o resto do fluxo normalmente. É o jeito da camada 4 de preservar a identidade do cliente numa conexão que o proxy abriu de novo, e não depende de protocolo, porque fica inteiramente abaixo da aplicação.
O que o proxy TCP não faz, e o que tentam fazer mesmo assim
Não roteia por conteúdo. O path da URL, um hostname num header, um cookie ou um método: tudo isso é invisível para ele. Qualquer requisito escrito como "mande /api para este pool" é um requisito de camada 7 disfarçado de pergunta de camada 4.
Não refaz uma requisição que falhou. Se um membro do pool cai no meio da conexão, o proxy só pode dar reset; ele não tem como saber se a requisição do cliente podia ser repetida com segurança, porque nunca fez o parsing de nenhuma.
Não inspeciona o que encaminha. Criptografado ou não, o payload é opaco, e isso é intencional: é por isso que a camada 4 é a escolha honesta para o tráfego que você decidiu não inspecionar, e a escolha errada para o tráfego que você precisa inspecionar.
Multiplica as conexões. São dois sockets por sessão, mantidos durante toda a vida da sessão, e isso vira uma tabela dimensionada pela concorrência, e não pela taxa de requisições. Conexões longas e ociosas são a causa mais comum de esgotamento, e a correção são timeouts ajustados à aplicação, e não os valores default.
O problema da persistência
Como não há cookie nem header para usar como chave, a persistência (a stickiness) na camada 4 costuma ser feita pelo endereço de origem. Isso funciona até que os clientes cheguem todos atrás de um único endereço, como um gateway corporativo, uma operadora móvel ou uma implantação de CGNAT (tradução de endereços de rede, ou NAT, em escala de operadora). Aí mil usuários ficam presos num mesmo membro, o que parece um algoritmo de balanceamento com defeito e é, na verdade, o algoritmo trabalhando com a informação que tem.
O panorama, por categoria
- Proxies de kernel e de código aberto: IPVS, HAProxy em modo TCP, NGINX stream e os load balancers baseados em eBPF que ficam por trás das grandes infraestruturas de nuvem.
- Application delivery controllers (): F5, Citrix, A10 e similares, em que a camada 4 é o caminho rápido e a camada 7 é a opção que você liga em cada virtual server.
- Load balancers de rede na nuvem, vendidos justamente pelo trade-off da camada 4: throughput alto, latência baixa, nenhuma visibilidade do conteúdo e, muitas vezes, preservação do endereço do cliente, coisa que o produto de camada 7 não faz.
- Tudo o que faz TLS passthrough, que é camada 4 por necessidade, já que a decisão foi não ficar com a chave.
Onde você encontra proxies TCP
Um load balancer ou ADC num modo de camada 4 só é um proxy TCP quando esse modo termina a conexão, e um modo rápido de camada 4 pode não terminar. No F5 BIG-IP, por exemplo, o profile FastL4 usa um caminho leve de camada 4, que trata pacote a pacote (com aceleração em hardware nas plataformas que suportam) exatamente para os casos em que você quer throughput e tratamento de conexões sem parsing de camada 7; já um virtual server do tipo Standard usa o full proxy TCP e pode empilhar processamento de HTTP e de TLS por cima. Os load balancers de nuvem expõem a mesma divisão como load balancers "de rede" (camada 4) e "de aplicação" (camada 7). O padrão é o mesmo em todo lugar: decida se você precisa ver a aplicação e, se não precisar, fique na camada 4, onde tudo é mais barato e mais genérico.