Fix 502
This commit is contained in:
@@ -71,13 +71,13 @@ A confirmação dessa migração é exibida dentro do próprio painel. Se a grav
|
||||
|
||||
Contas SSH e clientes VLESS/VMess do modo nativo podem usar `data_quota_bytes` com ação `block` ou `throttle`. O botão **Reset/Zerar tráfego** limpa apenas os contadores; não renova validade, senha ou configuração da conta. Não existe reset periódico automático no servidor: qualquer período comercial mostrado no site é independente e o reset ocorre somente por ação explícita no painel/API. O valor `max_conns` é aplicado no momento em que o usuário VLESS/VMess é autenticado e vale em conjunto para TCP, UDP, WebSocket, XHTTP e conexões Mux (uma conexão Mux autenticada conta como uma conexão, independentemente dos streams filhos).
|
||||
|
||||
O runtime nativo também possui limites globais para impedir crescimento sem controle de sockets, goroutines e sessões HTTP:
|
||||
As antigas chaves globais de admissão continuam no JSON somente para compatibilidade, mas são sempre normalizadas para `-1` (ilimitado), inclusive quando um `config.json` antigo ainda contém `4096`, `8192`, `32768` ou outro valor positivo:
|
||||
|
||||
- `max_concurrent_connections`: conexões de transporte TCP/TLS/WebSocket/XHTTP; padrão `32768`;
|
||||
- `max_concurrent_xhttp_requests`: mantido apenas para compatibilidade de configuração; o limite de requisições web fica desativado (`-1`) no XHTTP;
|
||||
- `xhttp_max_sessions`: sessões XHTTP ativas; padrão `32768`.
|
||||
- `max_concurrent_connections`: sem limite global de conexões de transporte;
|
||||
- `max_concurrent_xhttp_requests`: sem limite global de requisições XHTTP;
|
||||
- `xhttp_max_sessions`: sem limite global de sessões XHTTP.
|
||||
|
||||
Esses campos ficam em **Configurações → Xray → Native Xray scale tuning**. O XHTTP é tratado como transporte VPN: rajadas de packet-up usam backpressure cancelável e buffers de bytes limitados, sem respostas `429` nem semântica de “too many requests”. Ao atingir o teto de transporte, novos sockets permanecem no backlog do kernel em vez de serem aceitos e resetados. Conexões HTTP/2 mantêm um limite de fluxo de 1024 streams simultâneos por conexão. Cada transporte Mux aceita no máximo 64 sessões filhas, com limite global padrão de 32768. Sockets WebSocket incompletos têm timeout de handshake, conexões HTTP ociosas têm timeout, e parar/reiniciar o Xray nativo fecha conexões e sessões existentes. Atualizações de tráfego e de conexões ativas são agregadas e persistidas em lote a cada cinco segundos, sem criar uma goroutine ou consulta PostgreSQL por conexão. Entradas pendentes de usuários removidos são descartadas para manter os mapas de retry limitados ao conjunto atual de contas.
|
||||
O painel não expõe mais esses três controles como limites ajustáveis. Xray XHTTP e XHTTP SSH usam o mesmo listener VPN sem teto por quantidade de requisições, streams HTTP/2, conexões de transporte ou sessões XHTTP. Rajadas de `packet-up` e a remontagem fora de ordem usam backpressure cancelável contabilizado em bytes; até pacotes vazios consomem um custo mínimo de memória contabilizada, portanto remover o limite por quantidade não cria uma fila de metadados sem limite. Não existem respostas `429` nem rejeições `503` por capacidade global. As políticas reais por usuário (`max_conns`, cota e banda) continuam ativas. Cada transporte Mux aceita no máximo 64 sessões filhas, com limite global padrão de 32768. Parar/reiniciar o Xray nativo fecha conexões e sessões existentes. Atualizações de tráfego e de conexões ativas são agregadas e persistidas em lote a cada cinco segundos, sem criar uma goroutine ou consulta PostgreSQL por conexão. Entradas pendentes de usuários removidos são descartadas para manter os mapas de retry limitados ao conjunto atual de contas.
|
||||
|
||||
### Requisitos
|
||||
|
||||
@@ -612,13 +612,13 @@ The migration confirmation is rendered inside the panel. If saving fails, the te
|
||||
|
||||
SSH accounts and native-mode VLESS/VMess clients can use `data_quota_bytes` with either the `block` or `throttle` action. The **Reset traffic** action clears only usage counters; it does not renew expiry, change a password, or alter account settings. The server does not perform an automatic periodic reset: any commercial period shown on the website is independent, and counters reset only through an explicit panel/API action. `max_conns` is enforced when a native VLESS/VMess user is authenticated and is shared across TCP, UDP, WebSocket, XHTTP, and Mux transports (one authenticated Mux transport counts as one connection, regardless of its child streams).
|
||||
|
||||
The native runtime also has global ceilings that prevent unbounded socket, goroutine, and HTTP-session growth:
|
||||
The old global admission keys remain in JSON for compatibility, but they are always normalized to `-1` (unlimited), including when an old `config.json` still contains `4096`, `8192`, `32768`, or any other positive value:
|
||||
|
||||
- `max_concurrent_connections`: TCP/TLS/WebSocket/XHTTP transport connections; default `32768`;
|
||||
- `max_concurrent_xhttp_requests`: retained for configuration compatibility; the web-request cap is disabled (`-1`) for XHTTP;
|
||||
- `xhttp_max_sessions`: active XHTTP sessions; default `32768`.
|
||||
- `max_concurrent_connections`: no global transport-connection count cap;
|
||||
- `max_concurrent_xhttp_requests`: no global XHTTP-request count cap;
|
||||
- `xhttp_max_sessions`: no global XHTTP-session count cap.
|
||||
|
||||
These fields are available under **Settings → Xray → Native Xray scale tuning**. XHTTP is treated as VPN transport traffic: packet-up bursts use cancelable backpressure and bounded byte buffers, with no `429` or “too many requests” behavior. At the transport ceiling, new sockets remain in the kernel backlog instead of being accepted and reset. HTTP/2 connections retain a 1024-stream flow-control guard per connection. Each Mux transport accepts at most 64 child sessions, with a default global ceiling of 32768. Incomplete WebSocket handshakes time out, idle HTTP connections time out, and stopping/restarting native Xray closes existing transports and XHTTP sessions. Traffic and active-connection changes are aggregated and written in five-second batches rather than creating a PostgreSQL query or goroutine for every connection. Pending retry entries for deleted clients are removed so retry maps stay bounded by the current account set.
|
||||
The panel no longer exposes those three controls as adjustable ceilings. Xray XHTTP and XHTTP SSH share the same VPN listener with no count ceiling for requests, HTTP/2 streams, transport connections, or XHTTP sessions. Packet-up bursts and out-of-order reassembly use cancelable byte-accounted backpressure; even empty packets are charged a minimum accounted-memory cost, so removing the request-count limit does not create an unbounded metadata queue. There are no `429` responses or global-capacity `503` rejections. Real per-user policies (`max_conns`, quota, and bandwidth) remain active. Each Mux transport accepts at most 64 child sessions, with a default global ceiling of 32768. Stopping/restarting native Xray closes existing transports and XHTTP sessions. Traffic and active-connection changes are aggregated and written in five-second batches rather than creating a PostgreSQL query or goroutine for every connection. Pending retry entries for deleted clients are removed so retry maps stay bounded by the current account set.
|
||||
|
||||
### Requirements
|
||||
|
||||
|
||||
@@ -761,28 +761,24 @@ Object.assign(I18N_TEXT["en-US"], {
|
||||
"Native Xray scale tuning":"Native Xray scale tuning",
|
||||
"Go CPU threads (GOMAXPROCS)":"Go CPU threads (GOMAXPROCS)",
|
||||
"Global mux backend sessions":"Global mux backend sessions",
|
||||
"Global transport connections":"Global transport connections",
|
||||
"XHTTP web request cap":"XHTTP web request cap",
|
||||
"disabled for VPN traffic":"disabled for VPN traffic",
|
||||
"Active XHTTP sessions":"Active XHTTP sessions",
|
||||
"Transport and XHTTP admission":"Transport and XHTTP admission",
|
||||
"Unlimited for VPN traffic. There is no global HTTP request, HTTP/2 stream, transport-connection, or XHTTP-session count cap.":"Unlimited for VPN traffic. There is no global HTTP request, HTTP/2 stream, transport-connection, or XHTTP-session count cap.",
|
||||
"Trace every XHTTP/mux packet":"Trace every XHTTP/mux packet",
|
||||
"debug only, slows QUIC":"debug only, slows QUIC",
|
||||
"Apply high-traffic VPN defaults":"Apply high-traffic VPN defaults",
|
||||
"Apply safe defaults":"Apply safe defaults",
|
||||
"XHTTP is handled as VPN tunnel traffic: packet requests use bounded backpressure and are never rejected by an HTTP request-rate ceiling. At the transport ceiling, new sockets wait in the kernel backlog instead of being reset. Keep the safe defaults unless the server is sized and load-tested for the high-traffic profile. HTTP/2 retains a 1024-stream flow-control guard per connection, while upload memory stays globally bounded. Saved in the panel config and applied live on restart/reload.":"XHTTP is handled as VPN tunnel traffic: packet requests use bounded backpressure and are never rejected by an HTTP request-rate ceiling. At the transport ceiling, new sockets wait in the kernel backlog instead of being reset. Keep the safe defaults unless the server is sized and load-tested for the high-traffic profile. HTTP/2 retains a 1024-stream flow-control guard per connection, while upload memory stays globally bounded. Saved in the panel config and applied live on restart/reload."
|
||||
"XHTTP is handled as VPN tunnel traffic: packet requests and reassembly are limited only by bounded byte backpressure, never by a request count. Existing saved web-style caps are ignored automatically after update. Per-user max_conns, quota, and bandwidth policies still work normally.":"XHTTP is handled as VPN tunnel traffic: packet requests and reassembly are limited only by bounded byte backpressure, never by a request count. Existing saved web-style caps are ignored automatically after update. Per-user max_conns, quota, and bandwidth policies still work normally."
|
||||
});
|
||||
Object.assign(I18N_TEXT["pt-BR"], {
|
||||
"Sort by":"Ordenar por","Show":"Mostrar","All":"Todos","Offline":"Offline","Connections":"Conexões","Usage":"Uso","Quota reached":"Cota atingida","{visible} of {total} users":"{visible} de {total} usuários","No Xray users match this filter.":"Nenhum usuário Xray corresponde a este filtro.","No SSH users match this filter.":"Nenhum usuário SSH corresponde a este filtro.",
|
||||
"Native Xray scale tuning":"Ajustes de escala do Xray nativo",
|
||||
"Go CPU threads (GOMAXPROCS)":"Threads de CPU do Go (GOMAXPROCS)",
|
||||
"Global mux backend sessions":"Sessões globais de backend Mux",
|
||||
"Global transport connections":"Conexões globais de transporte",
|
||||
"XHTTP web request cap":"Limite web de requisições XHTTP",
|
||||
"disabled for VPN traffic":"desativado para tráfego VPN",
|
||||
"Active XHTTP sessions":"Sessões XHTTP ativas",
|
||||
"Transport and XHTTP admission":"Admissão de transporte e XHTTP",
|
||||
"Unlimited for VPN traffic. There is no global HTTP request, HTTP/2 stream, transport-connection, or XHTTP-session count cap.":"Ilimitado para tráfego VPN. Não existe limite global por quantidade de requisições HTTP, streams HTTP/2, conexões de transporte ou sessões XHTTP.",
|
||||
"Trace every XHTTP/mux packet":"Registrar cada pacote XHTTP/Mux",
|
||||
"debug only, slows QUIC":"somente debug, reduz a velocidade do QUIC",
|
||||
"Apply high-traffic VPN defaults":"Aplicar padrão VPN de alto tráfego",
|
||||
"Apply safe defaults":"Aplicar padrões seguros",
|
||||
"XHTTP is handled as VPN tunnel traffic: packet requests use bounded backpressure and are never rejected by an HTTP request-rate ceiling. At the transport ceiling, new sockets wait in the kernel backlog instead of being reset. Keep the safe defaults unless the server is sized and load-tested for the high-traffic profile. HTTP/2 retains a 1024-stream flow-control guard per connection, while upload memory stays globally bounded. Saved in the panel config and applied live on restart/reload.":"O XHTTP é tratado como tráfego de túnel VPN: as requisições de pacotes usam backpressure com memória limitada e nunca são rejeitadas por um limite de requisições web. Ao atingir o teto de transporte, novos sockets aguardam no backlog do kernel em vez de serem resetados. Mantenha os padrões seguros, exceto se o servidor estiver dimensionado e testado para o perfil de alto tráfego. O HTTP/2 mantém um controle de fluxo de 1024 streams por conexão, e a memória de upload continua limitada globalmente. Salvo na configuração do painel e aplicado ao vivo ao reiniciar ou recarregar."
|
||||
"XHTTP is handled as VPN tunnel traffic: packet requests and reassembly are limited only by bounded byte backpressure, never by a request count. Existing saved web-style caps are ignored automatically after update. Per-user max_conns, quota, and bandwidth policies still work normally.":"O XHTTP é tratado como tráfego de túnel VPN: requisições de pacotes e remontagem usam somente backpressure com limite de bytes, nunca limite por quantidade de requisições. Limites web antigos já salvos são ignorados automaticamente após a atualização. As regras por usuário de max_conns, cota e banda continuam funcionando normalmente."
|
||||
});
|
||||
|
||||
@@ -29,24 +29,24 @@ function toggleUdpgwFields(on) {
|
||||
}
|
||||
|
||||
|
||||
// Only operator-safe knobs remain. Transport buffers (HTTP/2 flow control, XHTTP
|
||||
// reorder buffer, mux/UDP buffers) are fixed to xray-core defaults in the backend
|
||||
// and are no longer exposed here, so they cannot be misconfigured.
|
||||
// Only operator-safe knobs remain. Global transport/XHTTP count admission is
|
||||
// fixed to unlimited; byte backpressure and protocol buffers stay internal so a
|
||||
// panel value cannot turn normal VPN traffic into HTTP overload responses.
|
||||
const XRAY_NATIVE_TUNING_DEFAULTS = {
|
||||
safe: {
|
||||
runtime_gomaxprocs: 0,
|
||||
mux_global_sessions: 32768,
|
||||
max_concurrent_connections: 32768,
|
||||
max_concurrent_connections: -1,
|
||||
max_concurrent_xhttp_requests: -1,
|
||||
xhttp_max_sessions: 32768,
|
||||
xhttp_max_sessions: -1,
|
||||
trace_packets: false,
|
||||
},
|
||||
"high": {
|
||||
runtime_gomaxprocs: 0,
|
||||
mux_global_sessions: 65536,
|
||||
max_concurrent_connections: 65536,
|
||||
max_concurrent_connections: -1,
|
||||
max_concurrent_xhttp_requests: -1,
|
||||
xhttp_max_sessions: 65536,
|
||||
xhttp_max_sessions: -1,
|
||||
trace_packets: false,
|
||||
},
|
||||
};
|
||||
@@ -54,9 +54,6 @@ const XRAY_NATIVE_TUNING_DEFAULTS = {
|
||||
const XRAY_NATIVE_TUNING_FIELDS = {
|
||||
runtime_gomaxprocs: "cfgXrayRuntimeGomaxprocs",
|
||||
mux_global_sessions: "cfgXrayMuxGlobalSessions",
|
||||
max_concurrent_connections: "cfgXrayMaxConnections",
|
||||
max_concurrent_xhttp_requests: "cfgXrayMaxXHTTPRequests",
|
||||
xhttp_max_sessions: "cfgXrayMaxXHTTPSessions",
|
||||
};
|
||||
|
||||
function setXrayNativeTuningDefaults(profile = "high") {
|
||||
@@ -80,6 +77,12 @@ function readXrayNativeTuning() {
|
||||
const el = document.getElementById(id);
|
||||
out[key] = parseInt(el?.value || "0", 10) || 0;
|
||||
});
|
||||
// These legacy JSON keys are intentionally fixed at unlimited. Keeping them
|
||||
// in saved configs makes upgrades/downgrades explicit without exposing web
|
||||
// request ceilings that do not belong on a VPN transport.
|
||||
out.max_concurrent_connections = -1;
|
||||
out.max_concurrent_xhttp_requests = -1;
|
||||
out.xhttp_max_sessions = -1;
|
||||
out.trace_packets = !!document.getElementById("cfgXrayTracePackets")?.checked;
|
||||
return out;
|
||||
}
|
||||
|
||||
+4
-6
@@ -1532,15 +1532,13 @@
|
||||
<div class="grid2" style="margin-top:10px;gap:8px;">
|
||||
<div class="field"><label>Go CPU threads (GOMAXPROCS)</label><input type="number" min="0" id="cfgXrayRuntimeGomaxprocs" placeholder="0 = all CPU cores"/></div>
|
||||
<div class="field"><label>Global mux backend sessions</label><input type="number" min="1" id="cfgXrayMuxGlobalSessions" placeholder="32768"/></div>
|
||||
<div class="field"><label>Global transport connections <span class="hint">0=32768, -1=unlimited</span></label><input type="number" min="-1" id="cfgXrayMaxConnections" placeholder="32768"/></div>
|
||||
<div class="field"><label>XHTTP web request cap <span class="hint">disabled for VPN traffic</span></label><input type="number" min="-1" id="cfgXrayMaxXHTTPRequests" value="-1" readonly/></div>
|
||||
<div class="field"><label>Active XHTTP sessions <span class="hint">0=32768, -1=unlimited</span></label><input type="number" min="-1" id="cfgXrayMaxXHTTPSessions" placeholder="32768"/></div>
|
||||
<div class="field" style="grid-column:1/-1;"><label>Transport and XHTTP admission</label><div class="hint">Unlimited for VPN traffic. There is no global HTTP request, HTTP/2 stream, transport-connection, or XHTTP-session count cap.</div></div>
|
||||
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;grid-column:1/-1"><input type="checkbox" id="cfgXrayTracePackets"/> Trace every XHTTP/mux packet <span class="hint">debug only, slows QUIC</span></label>
|
||||
<div class="card-actions" style="grid-column:1/-1;">
|
||||
<button class="btn btn-ghost btn-sm" type="button" onclick="setXrayNativeTuningDefaults('high')">Apply high-traffic VPN defaults</button>
|
||||
<button class="btn btn-ghost btn-sm" type="button" onclick="setXrayNativeTuningDefaults('safe')">Apply safe defaults</button>
|
||||
</div>
|
||||
<div class="hint" style="grid-column:1/-1;margin-top:-4px;">XHTTP is handled as VPN tunnel traffic: packet requests use bounded backpressure and are never rejected by an HTTP request-rate ceiling. At the transport ceiling, new sockets wait in the kernel backlog instead of being reset. Keep the safe defaults unless the server is sized and load-tested for the high-traffic profile. HTTP/2 retains a 1024-stream flow-control guard per connection, while upload memory stays globally bounded. Saved in the panel config and applied live on restart/reload.</div>
|
||||
<div class="hint" style="grid-column:1/-1;margin-top:-4px;">XHTTP is handled as VPN tunnel traffic: packet requests and reassembly are limited only by bounded byte backpressure, never by a request count. Existing saved web-style caps are ignored automatically after update. Per-user max_conns, quota, and bandwidth policies still work normally.</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
@@ -1580,14 +1578,14 @@
|
||||
<!-- app.js was split into ordered modules for maintainability. They are plain
|
||||
classic scripts sharing one global scope; `defer` preserves execution order,
|
||||
so behavior is identical to the old single file. Keep this load order. -->
|
||||
<script defer src="assets/js/01-core.js?v=20260720sshfilters1"></script>
|
||||
<script defer src="assets/js/01-core.js?v=20260722xhttpunlimited2"></script>
|
||||
<script defer src="assets/js/02-shell.js?v=20260714pamfix1"></script>
|
||||
<script defer src="assets/js/03-ssh-users.js?v=20260720sshfilters1"></script>
|
||||
<script defer src="assets/js/04-xray.js?v=20260720sshfilters1"></script>
|
||||
<script defer src="assets/js/05-resellers.js?v=20260714pamfix1"></script>
|
||||
<script defer src="assets/js/06-servers.js?v=20260714pamfix1"></script>
|
||||
<script defer src="assets/js/07-stats-logs.js?v=20260714pamfix1"></script>
|
||||
<script defer src="assets/js/08-server-config.js?v=20260719xhttp502fix1"></script>
|
||||
<script defer src="assets/js/08-server-config.js?v=20260722xhttpunlimited2"></script>
|
||||
<script defer src="assets/js/09-xray-wizard.js?v=20260714quota1"></script>
|
||||
<script defer src="assets/js/11-update-status.js?v=20260714pamfix1"></script>
|
||||
<script defer src="assets/js/12-bot.js?v=20260714pamfix1"></script>
|
||||
|
||||
+1
-1
@@ -674,7 +674,7 @@ ExecStart=${INSTALL_DIR}/sshpanel -config ${INSTALL_DIR}/config.json
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
User=root
|
||||
LimitNOFILE=65536
|
||||
LimitNOFILE=1048576
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
|
||||
+61
-49
@@ -217,11 +217,7 @@ func TestTrackedNativeConnectionsAreClosedOnShutdown(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloseAllXHTTPSessionsReleasesGlobalSlots(t *testing.T) {
|
||||
oldLimit := nativeTuneXHTTPMaxSessions.Load()
|
||||
nativeTuneXHTTPMaxSessions.Store(8)
|
||||
defer nativeTuneXHTTPMaxSessions.Store(oldLimit)
|
||||
|
||||
func TestXHTTPSessionsIgnoreLegacyGlobalCapAndReleaseCounters(t *testing.T) {
|
||||
before := nativeXHTTPSessions.Load()
|
||||
ib := &nativeInbound{xhttpMaxBufferedPosts: 2}
|
||||
for _, id := range []string{"one", "two"} {
|
||||
@@ -245,28 +241,14 @@ func TestCloseAllXHTTPSessionsReleasesGlobalSlots(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNegativeXHTTPSessionLimitMeansUnlimited(t *testing.T) {
|
||||
old := nativeTuneXHTTPMaxSessions.Load()
|
||||
nativeTuneXHTTPMaxSessions.Store(0)
|
||||
defer nativeTuneXHTTPMaxSessions.Store(old)
|
||||
if got := (&nativeInbound{}).xhttpMaxActiveSessions(); got != 0 {
|
||||
t.Fatalf("unlimited XHTTP session limit normalized to %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeProtocolGuardsRemainFinite(t *testing.T) {
|
||||
oldRequests := nativeTuneMaxXHTTPRequests.Load()
|
||||
defer nativeTuneMaxXHTTPRequests.Store(oldRequests)
|
||||
|
||||
// Zero is the internal representation of an explicitly disabled application
|
||||
// request counter. HTTP/2 must still retain a finite per-connection guard.
|
||||
nativeTuneMaxXHTTPRequests.Store(0)
|
||||
if got := nativeHTTP2MaxConcurrentStreams(); got != defaultNativeHTTP2MaxStreams {
|
||||
t.Fatalf("HTTP/2 stream guard = %d, want %d", got, defaultNativeHTTP2MaxStreams)
|
||||
}
|
||||
|
||||
nativeTuneMaxXHTTPRequests.Store(32)
|
||||
if got := nativeHTTP2MaxConcurrentStreams(); got != 32 {
|
||||
t.Fatalf("HTTP/2 stream guard did not honor lower request cap: %d", got)
|
||||
func TestNativeHTTP2StreamsIgnoreLegacyRequestCeiling(t *testing.T) {
|
||||
if got := nativeHTTP2MaxConcurrentStreams(); got != ^uint32(0) {
|
||||
t.Fatalf("HTTP/2 stream setting = %d, want unlimited uint32 range", got)
|
||||
}
|
||||
if got := nativeMuxMaxSessionLimit(); got != 64 {
|
||||
t.Fatalf("per-transport Mux session guard = %d, want 64", got)
|
||||
@@ -274,15 +256,6 @@ func TestNativeProtocolGuardsRemainFinite(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestXHTTPHandlerDoesNotApplyWebRequestCeiling(t *testing.T) {
|
||||
oldLimit := nativeTuneMaxXHTTPRequests.Load()
|
||||
oldActive := nativeXHTTPRequests.Load()
|
||||
nativeTuneMaxXHTTPRequests.Store(1)
|
||||
nativeXHTTPRequests.Store(1)
|
||||
defer func() {
|
||||
nativeTuneMaxXHTTPRequests.Store(oldLimit)
|
||||
nativeXHTTPRequests.Store(oldActive)
|
||||
}()
|
||||
|
||||
ib := &nativeInbound{transport: "xhttp", path: "/"}
|
||||
req := httptest.NewRequest(http.MethodOptions, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
@@ -292,18 +265,18 @@ func TestXHTTPHandlerDoesNotApplyWebRequestCeiling(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegacyXHTTPTuningMigratesToVPNDefaults(t *testing.T) {
|
||||
func TestPersistedXHTTPAdmissionTuningIsAlwaysUnlimited(t *testing.T) {
|
||||
got := normalizeNativeXrayTuning(&XrayNativeTuning{
|
||||
MuxGlobalSessions: 8192,
|
||||
MaxConcurrentConnections: 4096,
|
||||
MaxConcurrentXHTTPRequests: 8192,
|
||||
XHTTPMaxSessions: 4096,
|
||||
})
|
||||
if got.MuxGlobalSessions != defaultNativeMuxGlobalSessions ||
|
||||
got.MaxConcurrentConnections != defaultNativeMaxConnections ||
|
||||
if got.MuxGlobalSessions != 8192 ||
|
||||
got.MaxConcurrentConnections != -1 ||
|
||||
got.MaxConcurrentXHTTPRequests != defaultNativeMaxXHTTPRequests ||
|
||||
got.XHTTPMaxSessions != defaultNativeXHTTPMaxSessions {
|
||||
t.Fatalf("legacy tuning was not migrated: %+v", got)
|
||||
got.XHTTPMaxSessions != -1 {
|
||||
t.Fatalf("persisted admission limits were not removed: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,20 +298,20 @@ func TestXHTTPMetadataLengthIsBoundedBeforeSessionAllocation(t *testing.T) {
|
||||
|
||||
func TestXHTTPUploadMemoryIsReleasedOnReadAndClose(t *testing.T) {
|
||||
before := nativeXHTTPBufferedBytes.Load()
|
||||
q := newNativeXHTTPUploadQueue(4, 8)
|
||||
q := newNativeXHTTPUploadQueue(4, 512)
|
||||
|
||||
lease, ok := acquireNativeXHTTPMemory(8)
|
||||
accounted := nativeXHTTPAccountedPacketBytes(4)
|
||||
lease, ok := acquireNativeXHTTPMemory(accounted)
|
||||
if !ok {
|
||||
t.Fatal("failed to reserve XHTTP test memory")
|
||||
}
|
||||
lease.shrink(4)
|
||||
if err := q.push(context.Background(), nativeXHTTPPacket{Payload: []byte("test"), Seq: 0}, lease); err != nil {
|
||||
lease.release()
|
||||
t.Fatalf("queue push failed: %v", err)
|
||||
}
|
||||
lease.release() // transferred leases are a no-op for the producer.
|
||||
if got := nativeXHTTPBufferedBytes.Load(); got != before+4 {
|
||||
t.Fatalf("buffered bytes after push = %d, want %d", got, before+4)
|
||||
if got := nativeXHTTPBufferedBytes.Load(); got != before+accounted {
|
||||
t.Fatalf("buffered bytes after push = %d, want %d", got, before+accounted)
|
||||
}
|
||||
|
||||
buf := make([]byte, 4)
|
||||
@@ -349,7 +322,7 @@ func TestXHTTPUploadMemoryIsReleasedOnReadAndClose(t *testing.T) {
|
||||
t.Fatalf("buffered bytes after read = %d, want %d", got, before)
|
||||
}
|
||||
|
||||
lease, ok = acquireNativeXHTTPMemory(3)
|
||||
lease, ok = acquireNativeXHTTPMemory(nativeXHTTPAccountedPacketBytes(3))
|
||||
if !ok {
|
||||
t.Fatal("failed to reserve second XHTTP test memory")
|
||||
}
|
||||
@@ -366,10 +339,10 @@ func TestXHTTPUploadMemoryIsReleasedOnReadAndClose(t *testing.T) {
|
||||
|
||||
func TestXHTTPUploadQueueEnforcesPerSessionByteBudget(t *testing.T) {
|
||||
before := nativeXHTTPBufferedBytes.Load()
|
||||
q := newNativeXHTTPUploadQueue(4, 4)
|
||||
q := newNativeXHTTPUploadQueue(4, nativeXHTTPMinPacketAccountingBytes-1)
|
||||
defer q.close()
|
||||
|
||||
lease, ok := acquireNativeXHTTPMemory(5)
|
||||
lease, ok := acquireNativeXHTTPMemory(nativeXHTTPAccountedPacketBytes(5))
|
||||
if !ok {
|
||||
t.Fatal("failed to reserve XHTTP test memory")
|
||||
}
|
||||
@@ -386,10 +359,10 @@ func TestXHTTPUploadQueueEnforcesPerSessionByteBudget(t *testing.T) {
|
||||
|
||||
func TestXHTTPUploadQueueBackpressuresInsteadOfRejectingBurst(t *testing.T) {
|
||||
before := nativeXHTTPBufferedBytes.Load()
|
||||
q := newNativeXHTTPUploadQueue(2, 4)
|
||||
q := newNativeXHTTPUploadQueue(2, nativeXHTTPMinPacketAccountingBytes)
|
||||
defer q.close()
|
||||
|
||||
first, ok := acquireNativeXHTTPMemory(4)
|
||||
first, ok := acquireNativeXHTTPMemory(nativeXHTTPAccountedPacketBytes(4))
|
||||
if !ok {
|
||||
t.Fatal("failed to reserve first XHTTP payload")
|
||||
}
|
||||
@@ -399,7 +372,7 @@ func TestXHTTPUploadQueueBackpressuresInsteadOfRejectingBurst(t *testing.T) {
|
||||
}
|
||||
first.release()
|
||||
|
||||
second, ok := acquireNativeXHTTPMemory(4)
|
||||
second, ok := acquireNativeXHTTPMemory(nativeXHTTPAccountedPacketBytes(4))
|
||||
if !ok {
|
||||
t.Fatal("failed to reserve second XHTTP payload")
|
||||
}
|
||||
@@ -438,11 +411,50 @@ func TestXHTTPUploadQueueBackpressuresInsteadOfRejectingBurst(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestXHTTPReassemblyHasNoPacketRequestCountCeiling(t *testing.T) {
|
||||
before := nativeXHTTPBufferedBytes.Load()
|
||||
q := newNativeXHTTPUploadQueue(1, 4*nativeXHTTPMinPacketAccountingBytes)
|
||||
defer q.close()
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
for _, seq := range []uint64{3, 2, 1, 0} {
|
||||
lease, ok := acquireNativeXHTTPMemory(nativeXHTTPAccountedPacketBytes(1))
|
||||
if !ok {
|
||||
done <- errors.New("could not reserve packet memory")
|
||||
return
|
||||
}
|
||||
err := q.push(context.Background(), nativeXHTTPPacket{Payload: []byte{byte('a' + seq)}, Seq: seq}, lease)
|
||||
lease.release()
|
||||
if err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
done <- nil
|
||||
}()
|
||||
|
||||
buf := make([]byte, 1)
|
||||
for want := byte('a'); want <= byte('d'); want++ {
|
||||
n, err := q.Read(buf)
|
||||
if err != nil || n != 1 || buf[0] != want {
|
||||
t.Fatalf("reassembled packet = (%d, %v, %q), want %q", n, err, buf[:n], []byte{want})
|
||||
}
|
||||
}
|
||||
if err := <-done; err != nil {
|
||||
t.Fatalf("out-of-order burst was rejected: %v", err)
|
||||
}
|
||||
q.close()
|
||||
if got := nativeXHTTPBufferedBytes.Load(); got != before {
|
||||
t.Fatalf("reassembly test leaked %d buffered bytes (baseline %d)", got, before)
|
||||
}
|
||||
}
|
||||
|
||||
func TestXHTTPBodyReservationUsesActualContentLength(t *testing.T) {
|
||||
ib := &nativeInbound{xhttpMaxEachPostBytes: 1_000_000}
|
||||
req := httptest.NewRequest(http.MethodPost, "/session/0", strings.NewReader("small"))
|
||||
if got := ib.xhttpUploadReservationBytes(req); got != 5 {
|
||||
t.Fatalf("body reservation = %d, want actual payload length 5", got)
|
||||
if got := ib.xhttpUploadReservationBytes(req); got != nativeXHTTPMinPacketAccountingBytes {
|
||||
t.Fatalf("body reservation = %d, want minimum accounted packet size", got)
|
||||
}
|
||||
req.ContentLength = -1
|
||||
if got := ib.xhttpUploadReservationBytes(req); got != 1_000_000 {
|
||||
|
||||
@@ -480,6 +480,7 @@ write_sshpanel_systemd_override() {
|
||||
echo "[Service]"
|
||||
echo "Environment=PANEL_LOG_FILE=${INSTALL_DIR}/logs/panel.log"
|
||||
echo "Environment=PANEL_LOG_MAX_BYTES=${PANEL_LOG_MAX_BYTES}"
|
||||
echo "LimitNOFILE=1048576"
|
||||
echo "ExecStartPre="
|
||||
echo "ExecStartPre=${MKDIR_BIN} -p ${INSTALL_DIR}/logs"
|
||||
echo "ExecStartPre=${SH_BIN} -c '${MOUNTPOINT_BIN} -q ${INSTALL_DIR}/logs || ${MOUNT_BIN} -t tmpfs -o size=${LOG_TMPFS_SIZE},mode=0755 tmpfs ${INSTALL_DIR}/logs || true'"
|
||||
|
||||
+3
-3
@@ -204,9 +204,9 @@ func (s *nativeXrayServer) start(configFile string) error {
|
||||
}
|
||||
return fmt.Errorf("native xray: listen %s (shared XHTTP): %w", addr, err)
|
||||
}
|
||||
// Apply the global pre-authentication ceiling before net/http can spawn a
|
||||
// goroutine or begin a TLS handshake for the accepted socket.
|
||||
serveLn := limitNativeListener(ln)
|
||||
// Track accepted sockets so stop/reload can close them. VPN transports are
|
||||
// not subject to a global website-style connection ceiling.
|
||||
serveLn := trackNativeListener(ln)
|
||||
if group.security == "tls" {
|
||||
serveLn = tls.NewListener(serveLn, group.tlsConfig)
|
||||
}
|
||||
|
||||
+16
-41
@@ -35,11 +35,7 @@ func init() {
|
||||
|
||||
var (
|
||||
nativeTransportConnections atomic.Int64
|
||||
nativeTransportRejected atomic.Int64
|
||||
nativeXHTTPRequests atomic.Int64
|
||||
nativeXHTTPRequestsRejected atomic.Int64
|
||||
nativeXHTTPSessions atomic.Int64
|
||||
nativeXHTTPSessionsRejected atomic.Int64
|
||||
nativeClientConnsRejected atomic.Int64
|
||||
nativePreAuthRejected atomic.Int64
|
||||
|
||||
@@ -50,9 +46,9 @@ var (
|
||||
}{conns: make(map[*nativeCountedConn]struct{})}
|
||||
)
|
||||
|
||||
// acquireNativeCounter reserves one slot without blocking. Blocking the accept
|
||||
// loop or an HTTP handler when the process is already at its safety ceiling
|
||||
// would retain yet more sockets/goroutines, so overload is rejected promptly.
|
||||
// acquireNativeCounter tracks a counted resource and returns an exactly-once
|
||||
// release function. Native transport/XHTTP admission calls it with limit=0
|
||||
// because VPN traffic must not be rejected by a global website-style ceiling.
|
||||
func acquireNativeCounter(active *atomic.Int64, limit int) (func(), bool) {
|
||||
for {
|
||||
current := active.Load()
|
||||
@@ -100,30 +96,11 @@ func logNativePreAuthRejection(format string, args ...interface{}) {
|
||||
}
|
||||
|
||||
func acquireNativeTransportConnection() (func(), bool) {
|
||||
limit := nativeMaxConnectionLimit()
|
||||
release, ok := acquireNativeCounter(&nativeTransportConnections, limit)
|
||||
if !ok {
|
||||
logNativeLimitRejection("transport connection", &nativeTransportRejected, limit)
|
||||
}
|
||||
return release, ok
|
||||
}
|
||||
|
||||
func acquireNativeXHTTPRequest() (func(), bool) {
|
||||
limit := nativeMaxXHTTPRequestLimit()
|
||||
release, ok := acquireNativeCounter(&nativeXHTTPRequests, limit)
|
||||
if !ok {
|
||||
logNativeLimitRejection("XHTTP request", &nativeXHTTPRequestsRejected, limit)
|
||||
}
|
||||
return release, ok
|
||||
return acquireNativeCounter(&nativeTransportConnections, 0)
|
||||
}
|
||||
|
||||
func acquireNativeXHTTPSession() (func(), bool) {
|
||||
limit := nativeXHTTPMaxSessionLimit()
|
||||
release, ok := acquireNativeCounter(&nativeXHTTPSessions, limit)
|
||||
if !ok {
|
||||
logNativeLimitRejection("XHTTP session", &nativeXHTTPSessionsRejected, limit)
|
||||
}
|
||||
return release, ok
|
||||
return acquireNativeCounter(&nativeXHTTPSessions, 0)
|
||||
}
|
||||
|
||||
func configureNativeTransportSocket(c net.Conn) {
|
||||
@@ -215,9 +192,8 @@ func registerTrackedNativeTransportConn(c net.Conn, release func()) (net.Conn, b
|
||||
return counted, true
|
||||
}
|
||||
|
||||
// waitWrapTrackedNativeTransportConn is used by raw native accept loops. It
|
||||
// holds at most one already-accepted socket while capacity is busy, leaving the
|
||||
// rest in the kernel backlog instead of creating origin-side resets/502s.
|
||||
// waitWrapTrackedNativeTransportConn is used by raw native accept loops. Global
|
||||
// admission is unlimited; the loop remains only to coordinate listener shutdown.
|
||||
func waitWrapTrackedNativeTransportConn(c net.Conn) (net.Conn, bool) {
|
||||
if c == nil {
|
||||
return nil, false
|
||||
@@ -254,20 +230,19 @@ func closeAllNativeTransportConnections() {
|
||||
}
|
||||
}
|
||||
|
||||
// nativeLimitedListener applies the same pre-authentication ceiling to XHTTP
|
||||
// listeners. net/http receives only sockets that own a slot; when capacity is
|
||||
// busy, new sockets remain in the kernel backlog until a slot becomes available.
|
||||
type nativeLimitedListener struct {
|
||||
// nativeTrackingListener registers every accepted XHTTP socket so a live
|
||||
// stop/reload can close it. It counts sockets for diagnostics but never rejects
|
||||
// or delays one because of a global application limit.
|
||||
type nativeTrackingListener struct {
|
||||
net.Listener
|
||||
}
|
||||
|
||||
func (l nativeLimitedListener) Accept() (net.Conn, error) {
|
||||
func (l nativeTrackingListener) Accept() (net.Conn, error) {
|
||||
for {
|
||||
// Reserve before accepting. When the transport is at capacity, connections
|
||||
// remain queued by the kernel rather than being accepted and reset, which is
|
||||
// the behavior CDNs commonly report as an origin 502.
|
||||
release, ok := acquireNativeTransportConnection()
|
||||
if !ok {
|
||||
// Unlimited admission can only fail if this implementation changes. Avoid
|
||||
// accepting and resetting a socket if that ever happens.
|
||||
time.Sleep(nativeOverloadBackoff)
|
||||
continue
|
||||
}
|
||||
@@ -286,9 +261,9 @@ func (l nativeLimitedListener) Accept() (net.Conn, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func limitNativeListener(ln net.Listener) net.Listener {
|
||||
func trackNativeListener(ln net.Listener) net.Listener {
|
||||
if ln == nil {
|
||||
return nil
|
||||
}
|
||||
return nativeLimitedListener{Listener: ln}
|
||||
return nativeTrackingListener{Listener: ln}
|
||||
}
|
||||
|
||||
+13
-47
@@ -18,10 +18,11 @@ type XrayNativeTuning struct {
|
||||
const (
|
||||
defaultNativeRuntimeGOMAXPROCS = 0
|
||||
defaultNativeMuxGlobalSessions = 32768
|
||||
defaultNativeMaxConnections = 32768
|
||||
// XHTTP packet handlers are governed by HTTP/2 flow control and bounded byte
|
||||
// queues, not a website-style request ceiling. A negative configured value is
|
||||
// normalized to the internal unlimited representation.
|
||||
// Transport sockets, XHTTP requests, and XHTTP sessions are VPN traffic, not
|
||||
// website requests. Keep the legacy JSON fields for config compatibility, but
|
||||
// always normalize them to unlimited. Actual resource protection is provided by
|
||||
// socket/HTTP flow control and the bounded byte queues in xray_xhttp.go.
|
||||
defaultNativeMaxConnections = -1
|
||||
defaultNativeMaxXHTTPRequests = -1
|
||||
|
||||
fixedNativeMuxMaxSessions = 64
|
||||
@@ -29,8 +30,7 @@ const (
|
||||
fixedNativeMuxUDPReadBuffer = 256 * 1024
|
||||
fixedNativeMuxUDPWriteBuffer = 256 * 1024
|
||||
|
||||
defaultNativeXHTTPMaxSessions = 32768
|
||||
defaultNativeHTTP2MaxStreams = 1024
|
||||
defaultNativeXHTTPMaxSessions = -1
|
||||
// Packet-up posts are also protected by byte budgets in xray_xhttp.go. Keep
|
||||
// the default reorder queue modest so thousands of unauthenticated sessions
|
||||
// cannot consume large amounts of memory merely by allocating empty channel
|
||||
@@ -48,9 +48,6 @@ const (
|
||||
var (
|
||||
nativeTuneRuntimeGOMAXPROCS atomic.Int64
|
||||
nativeTuneMuxGlobalSessions atomic.Int64
|
||||
nativeTuneMaxConnections atomic.Int64
|
||||
nativeTuneMaxXHTTPRequests atomic.Int64
|
||||
nativeTuneXHTTPMaxSessions atomic.Int64
|
||||
nativeTuneTracePackets atomic.Bool
|
||||
)
|
||||
|
||||
@@ -63,33 +60,18 @@ func normalizeNativeXrayTuning(t *XrayNativeTuning) XrayNativeTuning {
|
||||
t = &XrayNativeTuning{}
|
||||
}
|
||||
out := *t
|
||||
// Migrate the two profiles written by older panel builds. Those defaults were
|
||||
// sized like a web service (4K/8K sessions and a global request cap) and cause
|
||||
// valid high-volume XHTTP VPN traffic to be rejected after an upgrade unless
|
||||
// the persisted values are translated here.
|
||||
legacySafe := out.MaxConcurrentConnections == 4096 && out.MaxConcurrentXHTTPRequests == 8192 && out.XHTTPMaxSessions == 4096
|
||||
legacy2K := out.MaxConcurrentConnections == 8192 && out.MaxConcurrentXHTTPRequests == 16384 && out.XHTTPMaxSessions == 8192
|
||||
if legacySafe || legacy2K {
|
||||
out.MuxGlobalSessions = defaultNativeMuxGlobalSessions
|
||||
out.MaxConcurrentConnections = defaultNativeMaxConnections
|
||||
out.MaxConcurrentXHTTPRequests = defaultNativeMaxXHTTPRequests
|
||||
out.XHTTPMaxSessions = defaultNativeXHTTPMaxSessions
|
||||
}
|
||||
if out.RuntimeGOMAXPROCS < 0 {
|
||||
out.RuntimeGOMAXPROCS = defaultNativeRuntimeGOMAXPROCS
|
||||
}
|
||||
if out.MuxGlobalSessions <= 0 {
|
||||
out.MuxGlobalSessions = defaultNativeMuxGlobalSessions
|
||||
}
|
||||
if out.MaxConcurrentConnections == 0 {
|
||||
// Ignore every old positive/zero admission ceiling. This migration is
|
||||
// deliberately unconditional so upgrading an existing server immediately
|
||||
// removes the old 4K/8K/32K web-style caps without requiring a panel save.
|
||||
out.MaxConcurrentConnections = defaultNativeMaxConnections
|
||||
}
|
||||
if out.MaxConcurrentXHTTPRequests == 0 {
|
||||
out.MaxConcurrentXHTTPRequests = defaultNativeMaxXHTTPRequests
|
||||
}
|
||||
if out.XHTTPMaxSessions == 0 {
|
||||
out.XHTTPMaxSessions = defaultNativeXHTTPMaxSessions
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -105,27 +87,12 @@ func applyNativeXrayTuning(t *XrayNativeTuning) XrayNativeTuning {
|
||||
runtime.GOMAXPROCS(gomax)
|
||||
nativeTuneRuntimeGOMAXPROCS.Store(int64(gomax))
|
||||
nativeTuneMuxGlobalSessions.Store(int64(out.MuxGlobalSessions))
|
||||
nativeTuneMaxConnections.Store(nativeLimitValue(out.MaxConcurrentConnections))
|
||||
nativeTuneMaxXHTTPRequests.Store(nativeLimitValue(out.MaxConcurrentXHTTPRequests))
|
||||
nativeTuneXHTTPMaxSessions.Store(nativeLimitValue(out.XHTTPMaxSessions))
|
||||
nativeTuneTracePackets.Store(out.TracePackets)
|
||||
return out
|
||||
}
|
||||
|
||||
// Native tuning limits use zero internally for unlimited. In configuration,
|
||||
// zero means "use the safe default" and any negative value disables the cap.
|
||||
func nativeLimitValue(v int) int64 {
|
||||
if v < 0 {
|
||||
return 0
|
||||
}
|
||||
return int64(v)
|
||||
}
|
||||
|
||||
func nativeRuntimeGOMAXPROCS() int { return int(nativeTuneRuntimeGOMAXPROCS.Load()) }
|
||||
func nativeMuxGlobalSessionLimit() int { return int(nativeTuneMuxGlobalSessions.Load()) }
|
||||
func nativeMaxConnectionLimit() int { return int(nativeTuneMaxConnections.Load()) }
|
||||
func nativeMaxXHTTPRequestLimit() int { return int(nativeTuneMaxXHTTPRequests.Load()) }
|
||||
func nativeXHTTPMaxSessionLimit() int { return int(nativeTuneXHTTPMaxSessions.Load()) }
|
||||
func nativeTracePacketsEnabled() bool { return nativeTuneTracePackets.Load() }
|
||||
|
||||
func nativeMuxMaxSessionLimit() int { return fixedNativeMuxMaxSessions }
|
||||
@@ -133,11 +100,10 @@ func nativeMuxUDPReadBufferSize() int { return fixedNativeMuxUDPReadBuffer }
|
||||
func nativeMuxUDPWriteBufferSize() int { return fixedNativeMuxUDPWriteBuffer }
|
||||
func nativeXHTTPBufferedPostLimit() int { return defaultNativeXHTTPBufferedPosts }
|
||||
func nativeHTTP2MaxConcurrentStreams() uint32 {
|
||||
limit := nativeMaxXHTTPRequestLimit()
|
||||
if limit <= 0 || limit > defaultNativeHTTP2MaxStreams {
|
||||
return defaultNativeHTTP2MaxStreams
|
||||
}
|
||||
return uint32(limit)
|
||||
// x/net/http2 otherwise installs its own finite default when this is zero.
|
||||
// Advertise the protocol's full uint32 range so ordinary packet-up bursts can
|
||||
// never be refused by a website-style concurrent-stream setting.
|
||||
return ^uint32(0)
|
||||
}
|
||||
func nativeMuxUDPIdleTimeout() time.Duration {
|
||||
return fixedNativeMuxUDPIdleMS * time.Millisecond
|
||||
|
||||
+35
-36
@@ -21,8 +21,6 @@ import (
|
||||
"golang.org/x/net/http2/h2c"
|
||||
)
|
||||
|
||||
const nativeXHTTPServerIdleTimeout = 90 * time.Second
|
||||
|
||||
const (
|
||||
nativeXHTTPMaxSessionIDBytes = 256
|
||||
nativeXHTTPMaxSequenceBytes = 20
|
||||
@@ -31,6 +29,10 @@ const (
|
||||
nativeXHTTPMaxBufferedPosts = 512
|
||||
nativeXHTTPMaxBufferedSessionBytes = 16 * 1024 * 1024
|
||||
nativeXHTTPMaxBufferedGlobalBytes = 128 * 1024 * 1024
|
||||
// Tiny/empty packet-up requests still retain queue metadata. Charge a minimum
|
||||
// amount against the byte budgets so the reassembly queue can be count-unlimited
|
||||
// without allowing zero-byte packets to grow the heap without bound.
|
||||
nativeXHTTPMinPacketAccountingBytes int64 = 256
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -181,7 +183,6 @@ func (ib *nativeInbound) serveXHTTPListener(ln net.Listener) {
|
||||
func (g *nativeXHTTPListener) serve(ln net.Listener) {
|
||||
defer xrayRecover(fmt.Sprintf("native xray shared XHTTP listener addr=%s", ln.Addr()))
|
||||
h2s := &http2.Server{
|
||||
IdleTimeout: nativeXHTTPServerIdleTimeout,
|
||||
MaxConcurrentStreams: nativeHTTP2MaxConcurrentStreams(),
|
||||
}
|
||||
handler := http.Handler(g)
|
||||
@@ -195,7 +196,6 @@ func (g *nativeXHTTPListener) serve(ln net.Listener) {
|
||||
srv := &http.Server{
|
||||
Handler: handler,
|
||||
ReadHeaderTimeout: 4 * time.Second,
|
||||
IdleTimeout: nativeXHTTPServerIdleTimeout,
|
||||
MaxHeaderBytes: g.headerSize,
|
||||
}
|
||||
if g.security == "tls" && g.tlsConfig != nil {
|
||||
@@ -560,16 +560,7 @@ func (ib *nativeInbound) upsertXHTTPSession(w http.ResponseWriter, id string) *n
|
||||
s.touch()
|
||||
return s
|
||||
}
|
||||
if max := ib.xhttpMaxActiveSessions(); max > 0 && len(ib.xhttpSessions) >= max {
|
||||
http.Error(w, "native XHTTP session capacity reached", http.StatusServiceUnavailable)
|
||||
logNativePreAuthRejection("native xray: xhttp session rejected inbound=%q active=%d limit=%d", ib.tag, len(ib.xhttpSessions), max)
|
||||
return nil
|
||||
}
|
||||
releaseSlot, ok := acquireNativeXHTTPSession()
|
||||
if !ok {
|
||||
http.Error(w, "native XHTTP global session capacity reached", http.StatusServiceUnavailable)
|
||||
return nil
|
||||
}
|
||||
releaseSlot, _ := acquireNativeXHTTPSession()
|
||||
s := &nativeXHTTPSession{
|
||||
id: id,
|
||||
queue: newNativeXHTTPUploadQueue(ib.xhttpMaxBufferedPosts, nativeXHTTPMaxBufferedSessionBytes),
|
||||
@@ -585,9 +576,7 @@ func (ib *nativeInbound) upsertXHTTPSession(w http.ResponseWriter, id string) *n
|
||||
}
|
||||
|
||||
func (ib *nativeInbound) xhttpMaxActiveSessions() int {
|
||||
// normalizeNativeXrayTuning already installs the safe default. A zero value
|
||||
// here therefore intentionally means the operator configured -1 (unlimited).
|
||||
return nativeXHTTPMaxSessionLimit()
|
||||
return 0
|
||||
}
|
||||
|
||||
func (ib *nativeInbound) reapUnconnectedXHTTPSession(id string, s *nativeXHTTPSession) {
|
||||
@@ -679,7 +668,7 @@ func (ib *nativeInbound) handleXHTTPPacketUpload(w http.ResponseWriter, r *http.
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
memory.shrink(int64(len(payload)))
|
||||
memory.shrink(nativeXHTTPAccountedPacketBytes(int64(len(payload))))
|
||||
xrayTracef("native xray: xhttp packet-up inbound=%q session=%q seq=%d payload=%d remote=%s", ib.tag, sess.id, seq, len(payload), r.RemoteAddr)
|
||||
if err := sess.queue.push(r.Context(), nativeXHTTPPacket{Payload: payload, Seq: seq}, memory); err != nil {
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
@@ -816,11 +805,18 @@ func (ib *nativeInbound) xhttpUploadReservationBytes(r *http.Request) int64 {
|
||||
placement := firstNonEmpty(ib.xhttpUplinkDataPlacement, xhttpPlacementBody)
|
||||
if placement == xhttpPlacementBody && r.ContentLength >= 0 {
|
||||
if r.ContentLength > maxBytes {
|
||||
return maxBytes
|
||||
return nativeXHTTPAccountedPacketBytes(maxBytes)
|
||||
}
|
||||
return r.ContentLength
|
||||
return nativeXHTTPAccountedPacketBytes(r.ContentLength)
|
||||
}
|
||||
return maxBytes
|
||||
return nativeXHTTPAccountedPacketBytes(maxBytes)
|
||||
}
|
||||
|
||||
func nativeXHTTPAccountedPacketBytes(payloadBytes int64) int64 {
|
||||
if payloadBytes < nativeXHTTPMinPacketAccountingBytes {
|
||||
return nativeXHTTPMinPacketAccountingBytes
|
||||
}
|
||||
return payloadBytes
|
||||
}
|
||||
|
||||
func (ib *nativeInbound) handleXHTTPStreamOne(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -1189,11 +1185,11 @@ type nativeXHTTPPacket struct {
|
||||
Reader io.ReadCloser
|
||||
Payload []byte
|
||||
Seq uint64
|
||||
accountedBytes int64
|
||||
}
|
||||
|
||||
type nativeXHTTPUploadQueue struct {
|
||||
pushedPackets chan nativeXHTTPPacket
|
||||
maxPackets int
|
||||
maxBytes int64
|
||||
|
||||
// readMu serializes the single decoded stream reader with close-time queue
|
||||
@@ -1227,7 +1223,6 @@ func newNativeXHTTPUploadQueue(maxPackets int, maxBytes int64) *nativeXHTTPUploa
|
||||
}
|
||||
return &nativeXHTTPUploadQueue{
|
||||
pushedPackets: make(chan nativeXHTTPPacket, maxPackets),
|
||||
maxPackets: maxPackets,
|
||||
maxBytes: maxBytes,
|
||||
closed: make(chan struct{}),
|
||||
spaceChanged: make(chan struct{}),
|
||||
@@ -1318,15 +1313,18 @@ func (q *nativeXHTTPUploadQueue) push(ctx context.Context, p nativeXHTTPPacket,
|
||||
}
|
||||
}()
|
||||
}
|
||||
payloadBytes := int64(len(p.Payload))
|
||||
if err := q.adoptPayloadMemory(ctx, memory, payloadBytes); err != nil {
|
||||
accountedBytes := int64(0)
|
||||
if p.Reader == nil {
|
||||
accountedBytes = nativeXHTTPAccountedPacketBytes(int64(len(p.Payload)))
|
||||
}
|
||||
if err := q.adoptPayloadMemory(ctx, memory, accountedBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
transferred := payloadBytes > 0
|
||||
if transferred {
|
||||
p.accountedBytes = accountedBytes
|
||||
if accountedBytes > 0 {
|
||||
defer func() {
|
||||
if payloadBytes > 0 {
|
||||
q.releasePayloadMemory(payloadBytes)
|
||||
if accountedBytes > 0 {
|
||||
q.releasePayloadMemory(accountedBytes)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -1334,7 +1332,7 @@ func (q *nativeXHTTPUploadQueue) push(ctx context.Context, p nativeXHTTPPacket,
|
||||
case q.pushedPackets <- p:
|
||||
// Ownership has moved to the queue. close() waits for this producer and
|
||||
// then drains/releases anything not consumed by the stream reader.
|
||||
payloadBytes = 0
|
||||
accountedBytes = 0
|
||||
readerReserved = false
|
||||
return nil
|
||||
case <-q.closed:
|
||||
@@ -1449,7 +1447,7 @@ func (q *nativeXHTTPUploadQueue) Read(b []byte) (int, error) {
|
||||
}
|
||||
select {
|
||||
case <-q.closed:
|
||||
q.releasePayloadMemory(int64(len(p.Payload)))
|
||||
q.releasePayloadMemory(p.accountedBytes)
|
||||
return 0, io.EOF
|
||||
default:
|
||||
}
|
||||
@@ -1461,20 +1459,21 @@ func (q *nativeXHTTPUploadQueue) Read(b []byte) (int, error) {
|
||||
|
||||
if packet.Seq == q.nextSeq {
|
||||
n := copy(b, packet.Payload)
|
||||
q.releasePayloadMemory(int64(n))
|
||||
if n < len(packet.Payload) {
|
||||
q.releasePayloadMemory(int64(n))
|
||||
packet.accountedBytes -= int64(n)
|
||||
packet.Payload = packet.Payload[n:]
|
||||
heap.Push(&q.heap, packet)
|
||||
} else {
|
||||
q.releasePayloadMemory(packet.accountedBytes)
|
||||
q.nextSeq = packet.Seq + 1
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
if packet.Seq > q.nextSeq {
|
||||
if len(q.heap) > q.maxPackets {
|
||||
return 0, errors.New("xhttp upload reassembly buffer too large")
|
||||
}
|
||||
// Do not apply a packet/request count ceiling. The per-session and global
|
||||
// accounted-byte budgets backpressure producers, including empty packets.
|
||||
heap.Push(&q.heap, packet)
|
||||
p, err := q.recv()
|
||||
if err != nil {
|
||||
@@ -1489,7 +1488,7 @@ func (q *nativeXHTTPUploadQueue) Read(b []byte) (int, error) {
|
||||
}
|
||||
|
||||
// A duplicate/late packet is discarded; release the bytes it owned.
|
||||
q.releasePayloadMemory(int64(len(packet.Payload)))
|
||||
q.releasePayloadMemory(packet.accountedBytes)
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
|
||||
Reference in New Issue
Block a user