02 - Servidor Descobre Porta do Cliente na Requisição
video: https://youtu.be/... no frontmatter da nota.Servidor Descobre Porta do Cliente na Requisição
1. Definição e Mecanismo Fundamental
Quando um cliente (por exemplo, um navegador) abre uma conexão TCP para um servidor web, ele escolhe aleatoriamente uma porta de origem efêmera (porta alta, normalmente entre 1024 e 65535) e usa como porta de destino a porta bem conhecida do serviço (80 para HTTP, 443 para HTTPS). O servidor, ao receber o segmento TCP, descobre a porta do cliente simplesmente lendo o campo Source Port do cabeçalho TCP. Não há negociação prévia nem protocolo auxiliar — a informação está literalmente em cada pacote recebido.
Estrutura do Cabeçalho TCP
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port | <- bytes 0-3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Os 2 primeiros bytes do cabeçalho TCP são sempre a porta de origem. O servidor web faz recvfrom() e o kernel entrega, junto com o payload, a struct sockaddr_in preenchida com o IP e a porta do cliente. É exatamente assim que o Apache, o NGINX e qualquer servidor descobrem para quem responder.
2. O Conceito de Socket (Quádrupla)
A unidade de identificação de uma conexão TCP é o socket pair (quádrupla):
{ IP_origem, Porta_origem, IP_destino, Porta_destino }
Um servidor que escuta em :80 pode atender milhares de clientes simultâneos porque o kernel mantém uma tabela hash com todas as conexões ativas, indexada por essa quádrupla. Quando um segmento chega, o kernel extrai os 4 campos do cabeçalho IP+TCP e faz lookup nessa tabela para entregar o payload ao processo correto. Por isso a porta do cliente é essencial: sem ela, o kernel não conseguiria diferenciar duas conexões vindas do mesmo IP.
Exemplo Prático
# Cliente 1 (porta 51000) e Cliente 2 (porta 51001), mesmo IP 192.168.1.10
# servidores web os veem como sockets distintos:
192.168.1.10:51000 <-> 203.0.113.10:80 (conexão 1)
192.168.1.10:51001 <-> 203.0.113.10:80 (conexão 2)
3. Faixas de Portas Efêmeras por Sistema Operacional
A escolha da porta de origem é feita pelo kernel do cliente, respeitando faixas configuráveis (RFC 6056 recomenda aleatorização para evitar previsibilidade):
| Sistema | Faixa Padrão | Configuração |
|---|---|---|
| Linux | 32768–60999 | cat /proc/sys/net/ipv4/ip_local_port_range |
| Windows 10/11 | 49152–65535 | netsh int ipv4 show dynamicport tcp |
| macOS | 49152–65535 | sysctl net.inet.ip.portrange |
| FreeBSD | 10000–65535 | sysctl net.inet.ip.portrange |
A IANA divide oficialmente as portas em três faixas (RFC 6335):
- 0–1023: well-known (sistemas Unix exigem root para bind).
- 1024–49151: registered (registradas na IANA para aplicações específicas).
- 49152–65535: dynamic/ephemeral (uso livre pelo kernel).
4. Como o Servidor Mantém a Sessão
O HTTP até a versão 1.0 tratava cada requisição como uma conexão nova: o servidor abria, respondia e fechava. A porta de origem mudava a cada conexão, o que era caro (overhead de handshake TCP). O HTTP/1.1 (RFC 7230) introduziu keep-alive por padrão, permitindo múltiplas requisições reusando a mesma porta de origem, reduzindo latência.
Identificação de Sessão HTTP
Como o HTTP é stateless, aplicações web precisam associar múltiplas requisições a um mesmo usuário. As estratégias comuns são:
- IP + Porta de origem: era comum nos anos 1990, mas quebra com NAT (vários usuários compartilham o IP público) e proxies.
- Cookies de sessão: o servidor envia
Set-Cookie: sessionid=abc123na primeira resposta e o cliente reenviaCookie: sessionid=abc123nas subsequentes. É o padrão atual. - Tokens JWT/URL rewriting: alternativas stateless onde o estado está embutido no token assinado.
A porta de origem nunca deve ser usada como identificador de sessão persistente, porque ela muda a cada nova conexão TCP.
5. Problemas com NAT e Proxies
NAT de Origem (SNAT)
Um roteador doméstico faz SNAT: reescreve o IP de origem do pacote do cliente (192.168.1.10) para o IP público (200.150.0.5) e também reescreve a porta de origem se houver colisão. O servidor web enxerga apenas o IP público e uma porta traduzida. Isso quebra a associação IP+porta = cliente único.
Proxies Reversos e L7
Proxies como NGINX, HAProxy e Cloudfront recebem a requisição do cliente, abrem uma conexão própria para o backend e repassam. Para o backend, a porta de origem é a do proxy, não a do cliente. Para resolver isso, o proxy injeta cabeçalhos HTTP:
X-Forwarded-For: 200.150.0.5, 10.0.0.1
X-Forwarded-Proto: https
X-Real-IP: 200.150.0.5
O padrão moderno é o Forwarded (RFC 7239), mais estruturado:
Forwarded: for=200.150.0.5;proto=https;by=10.0.0.1
Connection Tracking no Kernel
O Linux mantém a tabela conntrack (/proc/net/nf_conntrack) com todas as conexões NAT ativas. Cada entrada consome ~300 bytes. Em servidores de alto tráfego, é comum ajustar:
echo "net.netfilter.nf_conntrack_max = 1048576" >> /etc/sysctl.conf
sysctl -p
Quando a tabela enche, novas conexões são derrubadas e o cliente vê timeouts intermitentes.
6. Inspeção Prática
Ver a porta de origem escolhida pelo kernel
# Mostra sockets TCP ativos com porta de origem
ss -tn
# Acompanha em tempo real novas conexões
ss -tn state established '( dport = :80 or sport = :80 )'
Exemplo de saída:
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 192.168.1.10:51000 93.184.216.34:80
ESTAB 0 0 192.168.1.10:51001 93.184.216.34:80
Capturar com tcpdump para ver o cabeçalho
sudo tcpdump -ni eth0 -nn 'tcp and port 80' -X | head -20
Os primeiros 4 bytes após o cabeçalho Ethernet+IP são as portas TCP. O Wireshark decodifica visualmente.
No lado do servidor (NGINX)
log_format main '$remote_addr:$remote_port -> $server_addr:$server_port '
'"$request" $status';
access_log /var/log/nginx/access.log main;
A variável $remote_port contém a porta efêmera do cliente — útil para correlacionar logs de acesso com capturas de pacotes.
7. Conexões Simultâneas e Limites
Limite do Servidor
Em Linux, cada conexão TCP ocupa um slot na tabela de sockets do kernel. O limite de file descriptors por processo (default 1024) costuma ser o primeiro gargalo. Em servidores de produção:
ulimit -n 65535 # processo
echo "* soft nofile 65535" >> /etc/security/limits.conf
echo "fs.file-max = 2097152" >> /etc/sysctl.conf
Problema das Portas Esgotadas (TIME_WAIT)
Quando o servidor fecha a conexão, a porta entra em TIME_WAIT por 60–120 segundos (2 × MSL, RFC 793). Em servidores de alto volume, isso esgota as portas efêmeras (apenas ~28 mil disponíveis):
# Verificar
ss -s
# Mitigações
echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.conf # reutiliza TIME_WAIT
echo "net.ipv4.tcp_fin_timeout = 15" >> /etc/sysctl.conf
8. Tabela Resumo
| Conceito | Detalhe |
|---|---|
| Campo TCP | Bytes 0–1 do cabeçalho TCP |
| Faixa efêmera Linux | 32768–60999 |
| Faixa efêmera Windows | 49152–65535 |
| Quádrupla socket | {IP_src, Port_src, IP_dst, Port_dst} |
| Identificação de sessão HTTP | Cookies (não IP+porta) |
| Cabeçalho de proxy | X-Forwarded-For, Forwarded |
| Tabela NAT (Linux) | /proc/net/nf_conntrack |
9. Conclusão
Descobrir a porta do cliente é trivial do ponto de vista de protocolo — basta ler 2 bytes do cabeçalho TCP —, mas os desdobramentos operacionais são profundos. Em ambientes modernos com NAT, proxies, load balancers e CDNs, a porta de origem visível ao servidor raramente é a porta original do cliente. Engenheiros precisam entender essa cadeia de traduções para diagnosticar problemas de sessão, persistência (sticky sessions), auditoria de acesso e segurança (rate limiting por IP). Saber que a porta efêmera é escolhida pelo kernel, que o servidor a obtém do cabeçalho TCP e que ela não é confiável como identificador persistente é fundamental para arquiteturas de aplicação robustas.