feat(gateway): harden failover and payload handling

Retry pre-response transport failures across candidates with an explicit stop policy, and propagate end-to-end timing into usage records and UI diagnostics.

Remove legacy body, import, cookie, PII, and tunnel replay caps while preserving optional operator-configured gateway limits.
This commit is contained in:
elky
2026-07-30 01:03:27 +08:00
parent a97acc07fc
commit a04673a90d
80 changed files with 3640 additions and 1236 deletions
+18 -14
View File
@@ -3,7 +3,7 @@ use aether_admission_core::{
DefaultAdmissionPolicy, ResourceClass,
};
use crate::{DEFAULT_OWNER_RELAY_BODY_LIMIT_BYTES, DEFAULT_TUNNEL_PROBE_BODY_LIMIT_BYTES};
use crate::DEFAULT_TUNNEL_PROBE_BODY_LIMIT_BYTES;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TunnelAdmissionClass {
@@ -27,7 +27,7 @@ pub struct TunnelAdmissionPolicy {
impl TunnelAdmissionPolicy {
pub fn decide(&self, request: TunnelAdmissionRequest<'_>) -> AdmissionDecision {
let body_limit = body_limit(request.class);
if request.body_bytes > body_limit {
if body_limit.is_some_and(|limit| request.body_bytes > limit) {
return AdmissionDecision::Reject(AdmissionRejectReason::BodyTooLarge);
}
@@ -37,7 +37,7 @@ impl TunnelAdmissionPolicy {
body_bytes: request.body_bytes,
}) {
AdmissionDecision::Admit(mut budget) => {
budget.body_bytes = body_limit;
budget.body_bytes = body_limit.unwrap_or(0);
AdmissionDecision::Admit(budget)
}
rejected => rejected,
@@ -45,11 +45,11 @@ impl TunnelAdmissionPolicy {
}
}
const fn body_limit(class: TunnelAdmissionClass) -> usize {
const fn body_limit(class: TunnelAdmissionClass) -> Option<usize> {
match class {
TunnelAdmissionClass::Connection => 0,
TunnelAdmissionClass::Relay { .. } => DEFAULT_OWNER_RELAY_BODY_LIMIT_BYTES,
TunnelAdmissionClass::Probe => DEFAULT_TUNNEL_PROBE_BODY_LIMIT_BYTES,
TunnelAdmissionClass::Connection => Some(0),
TunnelAdmissionClass::Relay { .. } => None,
TunnelAdmissionClass::Probe => Some(DEFAULT_TUNNEL_PROBE_BODY_LIMIT_BYTES),
}
}
@@ -68,7 +68,7 @@ mod tests {
use super::{TunnelAdmissionClass, TunnelAdmissionPolicy, TunnelAdmissionRequest};
use aether_admission_core::{AdmissionDecision, AdmissionRejectReason, DbClass};
use crate::DEFAULT_OWNER_RELAY_BODY_LIMIT_BYTES;
use crate::DEFAULT_TUNNEL_PROBE_BODY_LIMIT_BYTES;
#[test]
fn stream_connection_reserves_stream_and_upstream_permits() {
@@ -86,23 +86,27 @@ mod tests {
}
#[test]
fn relay_budget_exposes_and_enforces_body_limit() {
fn relay_budget_has_no_body_limit() {
let policy = TunnelAdmissionPolicy::default();
let decision = policy.decide(TunnelAdmissionRequest {
trace_id: "trace-2",
class: TunnelAdmissionClass::Relay { streaming: false },
body_bytes: DEFAULT_OWNER_RELAY_BODY_LIMIT_BYTES,
body_bytes: usize::MAX,
});
let AdmissionDecision::Admit(budget) = decision else {
panic!("relay at the limit should be admitted");
panic!("relay body should be admitted without a size limit");
};
assert_eq!(budget.body_bytes, DEFAULT_OWNER_RELAY_BODY_LIMIT_BYTES);
assert_eq!(budget.body_bytes, 0);
}
#[test]
fn probe_budget_still_enforces_body_limit() {
let policy = TunnelAdmissionPolicy::default();
assert_eq!(
policy.decide(TunnelAdmissionRequest {
trace_id: "trace-3",
class: TunnelAdmissionClass::Relay { streaming: false },
body_bytes: DEFAULT_OWNER_RELAY_BODY_LIMIT_BYTES + 1,
class: TunnelAdmissionClass::Probe,
body_bytes: DEFAULT_TUNNEL_PROBE_BODY_LIMIT_BYTES + 1,
}),
AdmissionDecision::Reject(AdmissionRejectReason::BodyTooLarge)
);
+2 -2
View File
@@ -18,6 +18,6 @@ pub use hub::{
};
pub use relay::{
is_tunnel_heartbeat_path, is_tunnel_node_status_path, TunnelAttachmentRecord,
DEFAULT_OWNER_RELAY_BODY_LIMIT_BYTES, DEFAULT_TUNNEL_PROBE_BODY_LIMIT_BYTES, PROXY_TUNNEL_PATH,
TUNNEL_HEARTBEAT_PATH, TUNNEL_NODE_STATUS_PATH, TUNNEL_RELAY_PATH_PATTERN, TUNNEL_ROUTE_FAMILY,
DEFAULT_TUNNEL_PROBE_BODY_LIMIT_BYTES, PROXY_TUNNEL_PATH, TUNNEL_HEARTBEAT_PATH,
TUNNEL_NODE_STATUS_PATH, TUNNEL_RELAY_PATH_PATTERN, TUNNEL_ROUTE_FAMILY,
};
@@ -6,7 +6,6 @@ pub const TUNNEL_NODE_STATUS_PATH: &str = "/api/internal/tunnel/node-status";
pub const TUNNEL_RELAY_PATH_PATTERN: &str = "/api/internal/tunnel/relay/{node_id}";
pub const TUNNEL_ROUTE_FAMILY: &str = "tunnel_manage";
pub const DEFAULT_OWNER_RELAY_BODY_LIMIT_BYTES: usize = 5_242_880;
pub const DEFAULT_TUNNEL_PROBE_BODY_LIMIT_BYTES: usize = 64 * 1024;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]