Depuração de HTTP/3 e QUIC: 5 padrões de produção da captura de pacotes à análise de desempenho
Por que a depuração de HTTP/3 é 10x mais difícil que HTTP/2
HTTP/3 roda sobre QUIC sobre UDP, o que significa que 20 anos de experiência em depuração TCP são essencialmente inúteis. Pacotes TCP são diretamente legíveis no Wireshark, fluxos HTTP/2 são visíveis no Chrome DevTools — mas o tráfego QUIC é criptografado, tornando os pacotes ilegíveis; o multiplex de fluxos HTTP/3 ocorre na camada de transporte, invisível para ferramentas de nível de aplicação.
Em 2026, a adoção global de HTTP/3 ultrapassa 45% (dados do Cloudflare Radar). No entanto, quando os desenvolvedores encontram problemas, geralmente veem apenas "connection failed" sem como investigar. Este artigo resume 5 padrões de depuração validados em produção, da descriptografia de captura ao monitoramento em tempo real, ajudando a construir um kit de ferramentas de depuração HTTP/3 completo.
| Dimensão de depuração | Ferramenta HTTP/2 | Ferramenta HTTP/3 | Mudança de dificuldade |
|---|---|---|---|
| Captura de pacotes | Wireshark (legível diretamente) | Wireshark + SSLKEYLOG | Requer descriptografia de chaves |
| Registro de protocolo | Sem padrão | qlog (formato padronizado) | Novo conceito |
| Depuração de cliente | Chrome DevTools | Chrome NetLog | Nível inferior |
| Depuração CLI | curl -v | curl --http3 + variáveis de ambiente | Requer compilação |
| Monitoramento de produção | Métricas TCP | Métricas específicas QUIC | Sistema de métricas diferente |
Padrão 1: Captura e dissecação de pacotes QUIC no Wireshark
Problema central: QUIC é totalmente criptografado
QUIC integra TLS 1.3 diretamente no protocolo. Todos os frames, incluindo cabeçalhos, são criptografados. Capturas QUIC no Wireshark mostram apenas payloads UDP — os frames HTTP/3 internos não podem ser analisados. Para descriptografar o tráfego QUIC, você deve obter as chaves de sessão TLS.
Mecanismo SSLKEYLOGFILE
SSLKEYLOGFILE é o mecanismo padrão de depuração TLS. Clientes que o suportam (Chrome, Firefox, curl, Go) gravam as chaves de sessão TLS em um arquivo especificado. O Wireshark lê esse arquivo para descriptografar o tráfego.
# Definir variável de ambiente SSLKEYLOGFILE
export SSLKEYLOGFILE=/tmp/sslkeys.log
# Capturar tráfego HTTP/3 com curl (chaves gravadas automaticamente)
curl --http3 https://example.com -v
# Acessar com Chrome (chaves gravadas automaticamente)
google-chrome --ssl-key-log-file=/tmp/sslkeys.log
# Acessar com Firefox
export MOZ_LOG="ssl:5"
export SSLKEYLOGFILE=/tmp/sslkeys.log
firefox
Configuração de descriptografia QUIC no Wireshark
# 1. Iniciar captura no Wireshark (filtrar tráfego QUIC)
# Filtro de captura:
udp port 443
# 2. Configurar arquivo de log de chaves no Wireshark
# Edit -> Preferences -> Protocols -> TLS -> (Pre)-Master-Secret log filename
# Inserir: /tmp/sslkeys.log
# 3. Verificar sucesso da descriptografia QUIC
# Antes da descriptografia: pacotes QUIC mostram "Protected Payload, PKN: ..."
# Após a descriptografia: frames HTTP/3 visíveis (HEADERS, DATA, SETTINGS, etc.)
Análise de pacotes via linha de comando com tshark
# Capturar tráfego QUIC com descriptografia
tshark -i eth0 -f "udp port 443" \
-o "tls.keylog_file:/tmp/sslkeys.log" \
-Y "quic" \
-T fields \
-e quic.packet_type \
-e quic.frame_type \
-e ip.src \
-e ip.dst \
-e quic.stream_id
# Extrair cabeçalhos de requisição HTTP/3
tshark -i eth0 -f "udp port 443" \
-o "tls.keylog_file:/tmp/sslkeys.log" \
-Y "http3" \
-T fields \
-e http3.header \
-e http3.stream_id
# Analisar informações de handshake de conexão QUIC
tshark -i eth0 -f "udp port 443" \
-o "tls.keylog_file:/tmp/sslkeys.log" \
-Y "quic.connection_id" \
-T fields \
-e quic.connection_id \
-e quic.version
# Analisar perda de pacotes e retransmissão QUIC
tshark -i eth0 -f "udp port 443" \
-o "tls.keylog_file:/tmp/sslkeys.log" \
-Y "quic.frame_type == 0x02 || quic.frame_type == 0x03" \
-T fields \
-e quic.frame_type \
-e quic.ack_range_count \
-e quic.ack_delay
Exportação de chaves no servidor Go
package main
import (
"crypto/tls"
"fmt"
"log"
"net/http"
"os"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello HTTP/3!")
})
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{loadCert()},
NextProtos: []string{"h3"},
KeyLogWriter: keyLogWriter(),
}
server := &http.Server{
Addr: ":443",
Handler: mux,
TLSConfig: tlsConfig,
}
log.Fatal(server.ListenAndServeTLS("", ""))
}
func keyLogWriter() tls.KeyLogWriter {
f, err := os.OpenFile("/tmp/server-sslkeys.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
if err != nil {
log.Printf("Failed to open keylog file: %v", err)
return nil
}
return f
}
func loadCert() tls.Certificate {
cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
log.Fatal(err)
}
return cert
}
Identificação de versão QUIC
# Números de versão QUIC comuns
# RFC 9000 (QUIC v1): 0x00000001
# RFC 9369 (QUIC v2): 0x6b3343cf
# Google QUIC (gQUIC): 0x51303039 (Q039)
# IETF Draft 29: 0xff00001d
# Filtrar versão específica com tshark
tshark -i eth0 -f "udp port 443" \
-Y "quic.version == 0x00000001" \
-T fields \
-e quic.connection_id \
-e quic.version
Padrão 2: Análise e visualização qlog
O que é qlog
qlog é o formato de registro QUIC/HTTP/3 padronizado pela IETF (RFC 9657). Ele define um modelo de eventos unificado que permite a interoperabilidade entre diferentes implementações QUIC. Ao contrário de formatos proprietários, o qlog permite analisar logs de quiche, lsquic, quic-go, ngtcp2 e outras implementações com as mesmas ferramentas.
{
"qlog_version": "0.3",
"title": "QUIC Connection Debug Log",
"description": "HTTP/3 connection from client to example.com",
"trace": [
{
"vantage_point": {
"type": "client"
},
"title": "Client trace",
"event_fields": [
"relative_time",
"category",
"event_type",
"data"
],
"events": [
[0, "transport", "packet_sent", {
"packet_type": "initial",
"header": {
"packet_number": 0,
"version": "0x00000001",
"scid": "0x8394c8f03e515708",
"dcid": "0x06b8a0b3a1914fc2"
},
"frames": [
{
"frame_type": "crypto",
"offset": 0,
"length": 387
},
{
"frame_type": "padding"
}
]
}],
[0.536, "transport", "packet_received", {
"packet_type": "initial",
"header": {
"packet_number": 0
},
"frames": [
{
"frame_type": "crypto",
"offset": 0,
"length": 1256
}
]
}],
[1.024, "http", "frame_created", {
"stream_id": 0,
"frame": {
"frame_type": "headers",
"headers": [
{"name": ":method", "value": "GET"},
{"name": ":path", "value": "/"},
{"name": ":authority", "value": "example.com"},
{"name": ":scheme", "value": "https"}
]
}
}]
]
}
]
}
Coleta qlog no lado do servidor
# Nginx QUIC qlog configuration (1.27+)
http {
quic_log_dir /var/log/nginx/quic;
quic_log_level debug;
server {
listen 443 quic reuseport;
server_name example.com;
access_log /var/log/nginx/quic/access.log quic_format;
}
}
Coleta qlog com quic-go
package main
import (
"context"
"encoding/json"
"log"
"net"
"os"
"time"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/logging"
)
type qlogTracer struct {
file *os.File
}
func newQlogTracer(filepath string) (*qlogTracer, error) {
f, err := os.Create(filepath)
if err != nil {
return nil, err
}
return &qlogTracer{file: f}, nil
}
func (t *qlogTracer) Trace() *logging.ConnectionTracer {
return &logging.ConnectionTracer{
StartedConnection: func(local, remote net.Addr, srcConnID, destConnID logging.ConnectionID) {
event := map[string]interface{}{
"time": time.Now().Format(time.RFC3339Nano),
"category": "transport",
"event": "connection_started",
"data": map[string]interface{}{
"src_cid": srcConnID.String(),
"dest_cid": destConnID.String(),
},
}
t.writeEvent(event)
},
ReceivedPacket: func(hdr *logging.PacketHeader, size logging.ByteCount, frames []logging.Frame) {
event := map[string]interface{}{
"time": time.Now().Format(time.RFC3339Nano),
"category": "transport",
"event": "packet_received",
"data": map[string]interface{}{
"packet_type": hdr.Type.String(),
"packet_size": size,
"frame_count": len(frames),
},
}
t.writeEvent(event)
},
SentPacket: func(hdr *logging.PacketHeader, size logging.ByteCount, frames []logging.Frame) {
event := map[string]interface{}{
"time": time.Now().Format(time.RFC3339Nano),
"category": "transport",
"event": "packet_sent",
"data": map[string]interface{}{
"packet_type": hdr.Type.String(),
"packet_size": size,
"frame_count": len(frames),
},
}
t.writeEvent(event)
},
}
}
func (t *qlogTracer) writeEvent(event map[string]interface{}) {
data, _ := json.Marshal(event)
t.file.Write(data)
t.file.Write([]byte("\n"))
}
func main() {
tracer, err := newQlogTracer("/tmp/quic-connection.qlog")
if err != nil {
log.Fatal(err)
}
defer tracer.file.Close()
tlsConfig := &tls.Config{
NextProtos: []string{"h3"},
}
quicConfig := &quic.Config{
Tracer: func(ctx context.Context, p logging.Perspective, ci logging.ConnectionID) logging.ConnectionTracer {
return *tracer.Trace()
},
}
conn, err := quic.DialAddr(context.Background(), "example.com:443", tlsConfig, quicConfig)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
log.Printf("Connected via QUIC, version: %s", conn.ConnectionState().Version)
}
Ferramentas de visualização qlog
# Visualização qlog: qvis
# Ferramenta online: https://qvis.quictools.info/
# Enviar arquivo .qlog para visualização
# Instalar qlog-converter
npm install -g qlog-converter
# Converter qlog binário para formato JSON
qlog-converter -i binary.qlog -o json.qlog --format JSON
# Converter JSON qlog para texto legível
qlog-converter -i json.qlog -o readable.txt --format TEXT
# Gerar relatório de visualização
npm install -g qlog-visualizer
qlog-visualizer -i connection.qlog -o report.html
Análise de eventos-chave qlog
# Analisar temporização do handshake
cat connection.qlog | python3 -c "
import json, sys
data = json.load(sys.stdin)
events = data['trace'][0]['events']
handshake_events = [e for e in events if e[1] == 'transport' and 'packet' in e[2]]
for e in handshake_events[:10]:
print(f't={e[0]:.3f}s {e[2]} type={e[3].get(\"packet_type\", \"?\")}')
"
# Analisar perda de pacotes e retransmissão
cat connection.qlog | python3 -c "
import json, sys
data = json.load(sys.stdin)
events = data['trace'][0]['events']
loss_events = [e for e in events if 'loss' in str(e[2]).lower() or 'retransmit' in str(e[2]).lower()]
for e in loss_events:
print(f't={e[0]:.3f}s {e[2]} data={e[3]}')
print(f'Total loss events: {len(loss_events)}')
"
# Analisar temporização no nível de fluxo
cat connection.qlog | python3 -c "
import json, sys
data = json.load(sys.stdin)
events = data['trace'][0]['events']
stream_events = [e for e in events if e[1] == 'http' and 'stream' in str(e[3])]
for e in stream_events:
stream_id = e[3].get('stream_id', '?')
print(f't={e[0]:.3f}s stream={stream_id} {e[2]}')
"
Padrão 3: Chrome NetLog para depuração HTTP/3
Por que o DevTools não é suficiente
O painel Network do Chrome DevTools mostra apenas informações de nível de aplicação (cabeçalhos de requisição, corpos de resposta, temporização). Ele não consegue ver detalhes da camada de transporte QUIC (migração de conexão, status 0-RTT, prioridades de fluxo, recuperação de perda). Para depurar problemas de transporte HTTP/3, você deve usar o Chrome NetLog.
Iniciar o Chrome com captura NetLog
# Método 1: flags de linha de comando
google-chrome \
--enable-logging=netlog \
--net-log-capture-mode=Everything \
--net-log=/tmp/chrome-netlog.json
# Método 2: visão em tempo real chrome://net-internals
# 1. Navegue até chrome://net-internals/#export
# 2. Clique em "Start logging to disk"
# 3. Execute a operação que deseja depurar
# 4. Clique em "Stop logging"
# Método 3: Chrome DevTools Protocol
google-chrome --remote-debugging-port=9222
# Disparar NetLog via CDP
curl -s http://localhost:9222/json/version | python3 -m json.tool
Análise de eventos NetLog
# A saída NetLog é formato JSON contendo todos os eventos de rede
# Estrutura típica:
# {
# "constants": { ... },
# "events": [
# {"time": ..., "type": "HTTP3_SESSION_INITIALIZED", ...},
# {"time": ..., "type": "QUIC_SESSION_PACKET_SENT", ...},
# ...
# ]
# }
# Extrair eventos relacionados a HTTP/3
cat /tmp/chrome-netlog.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
events = data.get('events', [])
http3_events = [e for e in events if 'HTTP3' in e.get('type', '') or 'QUIC' in e.get('type', '')]
for e in http3_events[:50]:
print(f't={e.get(\"time\",0)/1000000:.3f}s type={e.get(\"type\",\"?\")}')
"
# Analisar temporização de estabelecimento de conexão QUIC
cat /tmp/chrome-netlog.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
events = data.get('events', [])
connect_events = [e for e in events if any(k in e.get('type','') for k in ['QUIC_CONNECT', 'QUIC_SESSION_CONNECT', 'HTTP3_SESSION'])]
for e in connect_events:
t = e.get('time', 0) / 1000000
print(f't={t:.3f}s {e.get(\"type\",\"?\")}')
params = e.get('params', {})
if params:
for k, v in params.items():
if k in ['host', 'quic_version', 'connection_id', 'error', 'is_alternative_service']:
print(f' {k}={v}')
"
# Verificar status 0-RTT
cat /tmp/chrome-netlog.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
events = data.get('events', [])
zrtt_events = [e for e in events if 'ZERO_RTT' in e.get('type', '') or 'early_data' in str(e.get('params', {})).lower()]
for e in zrtt_events:
print(f't={e.get(\"time\",0)/1000000:.3f}s {e.get(\"type\")} params={e.get(\"params\",{})}')
if not zrtt_events:
print('No 0-RTT events found')
"
# Analisar migração de conexão
cat /tmp/chrome-netlog.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
events = data.get('events', [])
migration_events = [e for e in events if 'MIGRATION' in e.get('type', '') or 'CONNECTION_MIGRATION' in e.get('type', '')]
for e in migration_events:
print(f't={e.get(\"time\",0)/1000000:.3f}s {e.get(\"type\")} params={e.get(\"params\",{})}')
if not migration_events:
print('No connection migration events found')
"
Depuração em tempo real com chrome://net-internals
Páginas-chave do chrome://net-internals:
#h3 - lista de sessões HTTP/3
#quic - lista e configuração de conexões QUIC
#sockets - estado de sockets UDP
#dns - resolução DNS (incluindo registros HTTPS)
#httpCache - estado do cache
#altSvc - conteúdo do cache Alt-Svc
# Via chrome://net-internals/#quic ver:
# - Conexões QUIC ativas
# - Versão, CID, status de cada conexão
# - Parâmetros de configuração QUIC
# - Informações de erro de conexão
# Via chrome://net-internals/#h3 ver:
# - Estado de sessão HTTP/3
# - Criação e fechamento de fluxos
# - Relações de dependência de prioridade
# - Estado de push
Depuração de cache Alt-Svc
# Ver cache Alt-Svc
# chrome://net-internals/#altSvc
# Problema comum: cache Alt-Svc expirado ou incorreto
# Limpar cache Alt-Svc:
# 1. Abrir chrome://net-internals/#altSvc
# 2. Clicar em "Clear alt-svc cache"
# 3. Revisitar o site alvo
# Forçar HTTP/3 (ignorar descoberta Alt-Svc)
google-chrome \
--origin-to-force-quic-on=example.com:443 \
--net-log=/tmp/chrome-forced-h3.json
Padrão 4: Depuração curl HTTP/3
Compilar curl com suporte a HTTP/3
# Compilar curl com HTTP/3 no Ubuntu 22.04+
# Método: boringssl + nghttp3 + ngtcp2
# Instalar dependências
sudo apt-get install -y build-essential cmake git
# Compilar boringssl
git clone https://boringssl.googlesource.com/boringssl
cd boringssl
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=on .
make -j$(nproc)
cd ..
# Compilar ngtcp2
git clone https://github.com/ngtcp2/ngtcp2
cd ngtcp2
autoreconf -fi
./configure --with-boringssl=$(pwd)/../boringssl \
BORINGSSL_CFLAGS="-I$(pwd)/../boringssl/include" \
BORINGSSL_LIBS="-L$(pwd)/../boringssl/build/ssl -L$(pwd)/../boringssl/build/crypto -lssl -lcrypto"
make -j$(nproc)
sudo make install
cd ..
# Compilar nghttp3
git clone https://github.com/ngtcp2/nghttp3
cd nghttp3
autoreconf -fi
./configure
make -j$(nproc)
sudo make install
cd ..
# Compilar curl com HTTP/3
git clone https://github.com/curl/curl
cd curl
autoreconf -fi
./configure --with-openssl=$(pwd)/../boringssl \
--with-ngtcp2=$(pwd)/../ngtcp2 \
--with-nghttp3=$(pwd)/../nghttp3 \
LDFLAGS="-Wl,-rpath,$(pwd)/../boringssl/build/ssl:$(pwd)/../boringssl/build/crypto"
make -j$(nproc)
sudo make install
cd ..
# Verificar
curl --version | grep -i http3
# A saída deve incluir: Features: ... HTTP3 ...
Depuração básica de requisição HTTP/3
# Enviar requisição HTTP/3 com saída detalhada
curl --http3 https://example.com -v
# Exemplo de saída:
# * Trying 93.184.216.34:443...
# * Connected to example.com (93.184.216.34) port 443
# * QUIC handshake successful
# * Connection #0 to host example.com left intact
# > GET / HTTP/3
# > Host: example.com
# > user-agent: curl/8.7.1
# > accept: */*
# >
# < HTTP/3 200
# < content-type: text/html; charset=UTF-8
# < date: Mon, 16 Jun 2026 10:00:00 GMT
# Forçar apenas HTTP/3 (sem fallback)
curl --http3-only https://example.com -v
# Testar 0-RTT
# Primeira requisição: handshake normal
curl --http3 https://example.com -w "time_connect: %{time_connect}\ntime_appconnect: %{time_appconnect}\ntime_total: %{time_total}\n" -o /dev/null -s
# Segunda requisição: 0-RTT (reutilizar sessão)
curl --http3 https://example.com -w "time_connect: %{time_connect}\ntime_appconnect: %{time_appconnect}\ntime_total: %{time_total}\n" -o /dev/null -s
Análise de temporização curl
# Saída de temporização completa
curl --http3 https://example.com \
-w "\n=== Timing Breakdown ===\n\
namelookup: %{time_namelookup}s\n\
connect: %{time_connect}s\n\
appconnect: %{time_appconnect}s\n\
pretransfer: %{time_pretransfer}s\n\
starttransfer: %{time_starttransfer}s\n\
total: %{time_total}s\n\
\n=== Connection Info ===\n\
remote_ip: %{remote_ip}\n\
remote_port: %{remote_port}\n\
scheme: %{scheme}\n\
http_version: %{http_version}\n\
" -o /dev/null -s
# Comparar tempo de conexão HTTP/2 vs HTTP/3
echo "=== HTTP/2 ==="
curl --http2 https://example.com \
-w "appconnect: %{time_appconnect}s starttransfer: %{time_starttransfer}s total: %{time_total}s\n" \
-o /dev/null -s
echo "=== HTTP/3 ==="
curl --http3 https://example.com \
-w "appconnect: %{time_appconnect}s starttransfer: %{time_starttransfer}s total: %{time_total}s\n" \
-o /dev/null -s
# Teste em lote (10 execuções, média)
for proto in h2 h3; do
total=0
for i in $(seq 1 10); do
t=$(curl --http${proto} https://example.com -w "%{time_total}" -o /dev/null -s 2>/dev/null)
total=$(echo "$total + $t" | bc)
done
avg=$(echo "scale=3; $total / 10" | bc)
echo "HTTP/${proto} average total time: ${avg}s"
done
Depuração com variáveis de ambiente curl
# Ativar registro interno QUIC
export SSLKEYLOGFILE=/tmp/curl-quic-keys.log
# Ativar registro detalhado ngtcp2
export NGTCP2_DEBUG_LOG=1
# Ativar registro detalhado nghttp3
export NGHTTP3_DEBUG_LOG=1
# Enviar requisição
curl --http3 https://example.com -v 2>&1 | tee /tmp/curl-h3-debug.log
# Analisar log
grep -i "handshake\|0-rtt\|stream\|frame" /tmp/curl-h3-debug.log
# Testar versão QUIC específica
curl --http3 https://example.com -v \
--quic-version v1 # RFC 9000 (QUIC v1)
curl --http3 https://example.com -v \
--quic-version v2 # RFC 9369 (QUIC v2)
Simular problemas de rede com curl
# Simular alta latência
curl --http3 https://example.com -v \
--limit-rate 100k \
--connect-timeout 5 \
--max-time 30
# Testar tratamento de timeout de conexão
curl --http3-only https://unreachable.example.com -v \
--connect-timeout 3 \
--max-time 10
# Testar descoberta Alt-Svc
# Primeiro obter cabeçalho Alt-Svc via HTTP/2
curl --http2 https://example.com -v -I 2>&1 | grep -i alt-svc
# Depois conectar manualmente via HTTP/3
curl --http3 https://example.com -v
# Enviar arquivo grande para testar controle de fluxo
dd if=/dev/urandom bs=1M count=100 2>/dev/null | \
curl --http3 https://example.com/upload -v \
-X POST \
-H "Content-Type: application/octet-stream" \
--data-binary @-
Padrão 5: Monitoramento QUIC de produção
Coleta de métricas QUIC com Prometheus
package main
import (
"log"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
quicConnectionsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "quic_connections_total",
Help: "Total number of QUIC connections",
},
[]string{"version", "status"},
)
quicConnectionDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "quic_connection_duration_seconds",
Help: "QUIC connection duration in seconds",
Buckets: prometheus.ExponentialBuckets(0.1, 2, 15),
},
[]string{"version"},
)
quicHandshakeDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "quic_handshake_duration_seconds",
Help: "QUIC handshake duration in seconds",
Buckets: prometheus.ExponentialBuckets(0.001, 2, 15),
},
[]string{"version", "zero_rtt"},
)
quicStreamsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "quic_streams_total",
Help: "Total number of QUIC streams",
},
[]string{"direction", "stream_type"},
)
quicPacketsLost = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "quic_packets_lost_total",
Help: "Total number of QUIC packets lost",
},
[]string{"packet_type"},
)
quicRetransmitPackets = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "quic_retransmit_packets_total",
Help: "Total number of QUIC retransmit packets",
},
[]string{"packet_type"},
)
quicBytesTransferred = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "quic_bytes_transferred_total",
Help: "Total bytes transferred over QUIC",
},
[]string{"direction"},
)
quicConnectionMigrations = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "quic_connection_migrations_total",
Help: "Total number of QUIC connection migrations",
},
)
quicZeroRTTAccepts = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "quic_zero_rtt_accepts_total",
Help: "Total number of 0-RTT connection attempts",
},
[]string{"status"},
)
quicCongestionWindow = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "quic_congestion_window_bytes",
Help: "Current QUIC congestion window size in bytes",
},
[]string{"connection_id"},
)
quicRtt = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "quic_rtt_seconds",
Help: "QUIC round trip time in seconds",
Buckets: prometheus.ExponentialBuckets(0.001, 2, 15),
},
[]string{"rtt_type"},
)
)
func init() {
prometheus.MustRegister(
quicConnectionsTotal,
quicConnectionDuration,
quicHandshakeDuration,
quicStreamsTotal,
quicPacketsLost,
quicRetransmitPackets,
quicBytesTransferred,
quicConnectionMigrations,
quicZeroRTTAccepts,
quicCongestionWindow,
quicRtt,
)
}
func main() {
http.Handle("/metrics", promhttp.Handler())
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.ProtoMajor == 3 {
quicConnectionsTotal.WithLabelValues("v1", "active").Inc()
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("Hello HTTP/3!"))
})
log.Println("Starting server on :443 with metrics on :9090/metrics")
go func() {
log.Fatal(http.ListenAndServe(":9090", nil))
}()
log.Fatal(http.ListenAndServeTLS(":443", "server.crt", "server.key", mux))
}
Exportação de métricas QUIC no Nginx
# nginx.conf - QUIC metrics via stub_status
http {
server {
listen 443 quic reuseport;
listen 443 ssl;
server_name example.com;
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
log_format quic_metrics
'$remote_addr '
'$quic_connection_id '
'$quic_version '
'$request_time '
'$upstream_response_time '
'$bytes_sent '
'$bytes_received '
'$status';
access_log /var/log/nginx/quic-metrics.log quic_metrics;
}
}
# Usar nginx-prometheus-exporter para coletar métricas do Nginx
docker run -d --name nginx-exporter \
-p 9113:9113 \
nginx/nginx-prometheus-exporter:1.3 \
--nginx.scrape-uri=http://nginx:80/nginx_status
# Analisador de log QUIC personalizado (Python)
cat << 'EOF' > /usr/local/bin/quic-log-parser.py
import sys
from prometheus_client import Counter, Gauge, start_http_server
quic_connections = Counter('nginx_quic_connections', 'Nginx QUIC connections')
quic_request_duration = Gauge('nginx_quic_request_duration_seconds', 'QUIC request duration')
for line in sys.stdin:
parts = line.strip().split()
if len(parts) >= 8:
quic_connections.inc()
try:
duration = float(parts[3])
quic_request_duration.set(duration)
except ValueError:
pass
start_http_server(9114)
EOF
Configuração do painel Grafana
{
"dashboard": {
"title": "HTTP/3 & QUIC Production Monitoring",
"panels": [
{
"title": "QUIC Connection Rate",
"type": "timeseries",
"targets": [
{
"expr": "rate(quic_connections_total[5m])",
"legendFormat": "{{version}} {{status}}"
}
]
},
{
"title": "Handshake Duration",
"type": "timeseries",
"targets": [
{
"expr": "histogram_quantile(0.50, rate(quic_handshake_duration_seconds_bucket[5m]))",
"legendFormat": "p50 {{version}} 0-rtt={{zero_rtt}}"
},
{
"expr": "histogram_quantile(0.99, rate(quic_handshake_duration_seconds_bucket[5m]))",
"legendFormat": "p99 {{version}} 0-rtt={{zero_rtt}}"
}
]
},
{
"title": "Packet Loss Rate",
"type": "timeseries",
"targets": [
{
"expr": "rate(quic_packets_lost_total[5m]) / rate(quic_connections_total[5m])",
"legendFormat": "{{packet_type}} loss rate"
}
]
},
{
"title": "0-RTT Success Rate",
"type": "gauge",
"targets": [
{
"expr": "rate(quic_zero_rtt_accepts_total{status=\"accepted\"}[5m]) / rate(quic_zero_rtt_accepts_total[5m]) * 100",
"legendFormat": "0-RTT Accept %"
}
]
},
{
"title": "Connection Migrations",
"type": "stat",
"targets": [
{
"expr": "rate(quic_connection_migrations_total[1h])",
"legendFormat": "migrations/hour"
}
]
},
{
"title": "QUIC RTT Distribution",
"type": "timeseries",
"targets": [
{
"expr": "histogram_quantile(0.50, rate(quic_rtt_seconds_bucket[5m]))",
"legendFormat": "p50 {{rtt_type}}"
},
{
"expr": "histogram_quantile(0.95, rate(quic_rtt_seconds_bucket[5m]))",
"legendFormat": "p95 {{rtt_type}}"
}
]
},
{
"title": "Throughput",
"type": "timeseries",
"targets": [
{
"expr": "rate(quic_bytes_transferred_total{direction=\"send\"}[5m]) * 8 / 1000000",
"legendFormat": "Upload Mbps"
},
{
"expr": "rate(quic_bytes_transferred_total{direction=\"receive\"}[5m]) * 8 / 1000000",
"legendFormat": "Download Mbps"
}
]
},
{
"title": "Retransmit Rate",
"type": "timeseries",
"targets": [
{
"expr": "rate(quic_retransmit_packets_total[5m])",
"legendFormat": "{{packet_type}} retransmits/s"
}
]
}
]
}
}
Regras de alerta
# Prometheus alerting rules for QUIC
groups:
- name: quic_alerts
rules:
- alert: QUICHighPacketLoss
expr: rate(quic_packets_lost_total[5m]) / rate(quic_connections_total[5m]) > 0.05
for: 10m
labels:
severity: warning
annotations:
summary: "QUIC packet loss rate exceeds 5%"
description: "QUIC packet loss rate is {{ $value | humanizePercentage }} on {{ $labels.packet_type }}"
- alert: QUICHandshakeTimeout
expr: histogram_quantile(0.99, rate(quic_handshake_duration_seconds_bucket[5m])) > 1
for: 5m
labels:
severity: critical
annotations:
summary: "QUIC handshake p99 latency exceeds 1s"
description: "QUIC {{ $labels.version }} handshake p99 is {{ $value }}s"
- alert: QUICZeroRTTRejectionHigh
expr: rate(quic_zero_rtt_accepts_total{status="rejected"}[5m]) / rate(quic_zero_rtt_accepts_total[5m]) > 0.3
for: 15m
labels:
severity: warning
annotations:
summary: "QUIC 0-RTT rejection rate exceeds 30%"
description: "0-RTT rejection rate is {{ $value | humanizePercentage }}"
- alert: QUICConnectionFailureSpike
expr: rate(quic_connections_total{status="failed"}[5m]) > rate(quic_connections_total{status="active"}[5m]) * 0.1
for: 5m
labels:
severity: critical
annotations:
summary: "QUIC connection failure rate exceeds 10%"
description: "QUIC connection failures are {{ $value | humanizePercentage }} of successful connections"
- alert: QUICHighRetransmitRate
expr: rate(quic_retransmit_packets_total[5m]) / rate(quic_bytes_transferred_total[5m]) > 0.1
for: 10m
labels:
severity: warning
annotations:
summary: "QUIC retransmit rate exceeds 10%"
description: "QUIC retransmit rate is {{ $value | humanizePercentage }}"
Comparação dos 5 padrões de depuração
| Padrão | Caso de uso | Pontos fortes | Limitações | Curva de aprendizado |
|---|---|---|---|---|
| Captura Wireshark | Problemas de nível de protocolo, análise de handshake | Mais profunda, mais completa | Requer chaves, sem análise ao vivo | Alta |
| Análise qlog | Comparação entre implementações, otimização | Padronizada, visualizável | Requer suporte de implementação | Média |
| Chrome NetLog | Problemas de cliente, migração de conexão | Nativa do navegador, em tempo real | Apenas Chrome, grande volume de dados | Média |
| Depuração curl | Validação rápida, integração CI | CLI, scriptável | Requer compilação, funções limitadas | Baixa |
| Monitoramento de produção | Alertas de produção, análise de tendências | Em tempo real, visão global | Requer instrumentação, sem detalhe recordado | Média-Alta |
Referência rápida de 10 cenários comuns de depuração
| # | Cenário | Padrão recomendado | Comando/ferramenta-chave |
|---|---|---|---|
| 1 | Falha de handshake QUIC | Wireshark | tshark -f "udp port 443" -o "tls.keylog_file:keys.log" -Y "quic.packet_type==initial" |
| 2 | 0-RTT rejeitado | Chrome NetLog | chrome://net-internals/#quic encontrar eventos ZERO_RTT |
| 3 | Migração de conexão não funciona | Chrome NetLog | --origin-to-force-quic-on + eventos MIGRATION do NetLog |
| 4 | Recuperação de perda lenta | qlog | qvis visualizar eventos de detecção e recuperação de perda |
| 5 | Problemas de prioridade de fluxo | Wireshark | Descriptografar e analisar frames HTTP/3 PRIORITY |
| 6 | Alt-Svc não funciona | curl | curl -I https://example.com verificar cabeçalho Alt-Svc |
| 7 | Comparação de desempenho | curl | comparação de temporização curl --http2 vs curl --http3 |
| 8 | Perda de pacotes anormal em produção | Prometheus | alerta rate(quic_packets_lost_total[5m]) |
| 9 | Latência de handshake elevada | Prometheus | histogram_quantile(0.99, quic_handshake_duration_seconds) |
| 10 | Risco de replay 0-RTT | Wireshark + qlog | Capturar e verificar dados 0-RTT para requisições não idempotentes |
Ferramentas recomendadas
Durante a depuração HTTP/3, estas ferramentas podem ajudar a analisar e verificar:
- Busca de código de status HTTP: Use /pt-BR/network/http-status para consultar os significados de códigos de status relacionados a QUIC/HTTP3
- Codificador Base64: Use /pt-BR/encode/base64 para codificar/decodificar certificados e tokens
- Calculadora de hash: Use /pt-BR/encode/hash para calcular valores de hash para a integridade da configuração QUIC
Resumo: O desafio central da depuração HTTP/3 reside na criptografia completa e no transporte UDP do QUIC. Os 5 padrões de depuração de produção têm focos diferentes: captura Wireshark para análise profunda de nível de protocolo (requer descriptografia SSLKEYLOGFILE), qlog para análise de log padronizada entre implementações, Chrome NetLog para depuração em tempo real no lado do cliente, curl para validação rápida e integração CI, e Prometheus+Grafana para monitoramento contínuo em produção. Comece com curl para validação rápida, escale para Wireshark/qlog para problemas profundos, e construa sempre um sistema completo de métricas QUIC de monitoramento e alertas em produção.
Experimente estas ferramentas executadas localmente no navegador — nenhum cadastro necessário →