feat(smart-proxy): add UDP transport support with QUIC/HTTP3 routing and datagram handler relay

This commit is contained in:
2026-03-19 15:06:27 +00:00
parent cfa958cf3d
commit 4fb91cd868
34 changed files with 2978 additions and 55 deletions

View File

@@ -15,6 +15,7 @@ pub fn create_http_route(
domains: Some(domains.into()),
path: None,
client_ip: None,
transport: None,
tls_version: None,
headers: None,
protocol: None,
@@ -31,6 +32,7 @@ pub fn create_http_route(
send_proxy_protocol: None,
headers: None,
advanced: None,
backend_transport: None,
priority: None,
}]),
tls: None,
@@ -41,6 +43,7 @@ pub fn create_http_route(
forwarding_engine: None,
nftables: None,
send_proxy_protocol: None,
udp: None,
},
headers: None,
security: None,
@@ -107,6 +110,7 @@ pub fn create_http_to_https_redirect(
domains: Some(domains),
path: None,
client_ip: None,
transport: None,
tls_version: None,
headers: None,
protocol: None,
@@ -137,6 +141,7 @@ pub fn create_http_to_https_redirect(
forwarding_engine: None,
nftables: None,
send_proxy_protocol: None,
udp: None,
},
headers: None,
security: None,
@@ -187,6 +192,7 @@ pub fn create_load_balancer_route(
send_proxy_protocol: None,
headers: None,
advanced: None,
backend_transport: None,
priority: None,
})
.collect();
@@ -200,6 +206,7 @@ pub fn create_load_balancer_route(
domains: Some(domains.into()),
path: None,
client_ip: None,
transport: None,
tls_version: None,
headers: None,
protocol: None,
@@ -218,6 +225,7 @@ pub fn create_load_balancer_route(
forwarding_engine: None,
nftables: None,
send_proxy_protocol: None,
udp: None,
},
headers: None,
security: None,

View File

@@ -7,16 +7,24 @@ use crate::security_types::RouteSecurity;
// ─── Port Range ──────────────────────────────────────────────────────
/// Port range specification format.
/// Matches TypeScript: `type TPortRange = number | number[] | Array<{ from: number; to: number }>`
/// Matches TypeScript: `type TPortRange = number | Array<number | { from: number; to: number }>`
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PortRange {
/// Single port number
Single(u16),
/// Array of port numbers
List(Vec<u16>),
/// Array of port ranges
Ranges(Vec<PortRangeSpec>),
/// Array of port numbers, ranges, or mixed
List(Vec<PortRangeItem>),
}
/// A single item in a port range array: either a number or a from-to range.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PortRangeItem {
/// Single port number
Port(u16),
/// A from-to port range
Range(PortRangeSpec),
}
impl PortRange {
@@ -24,9 +32,11 @@ impl PortRange {
pub fn to_ports(&self) -> Vec<u16> {
match self {
PortRange::Single(p) => vec![*p],
PortRange::List(ports) => ports.clone(),
PortRange::Ranges(ranges) => {
ranges.iter().flat_map(|r| r.from..=r.to).collect()
PortRange::List(items) => {
items.iter().flat_map(|item| match item {
PortRangeItem::Port(p) => vec![*p],
PortRangeItem::Range(r) => (r.from..=r.to).collect(),
}).collect()
}
}
}
@@ -95,6 +105,10 @@ pub struct RouteMatch {
/// Listen on these ports (required)
pub ports: PortRange,
/// Transport protocol: tcp (default), udp, or all (both TCP and UDP)
#[serde(skip_serializing_if = "Option::is_none")]
pub transport: Option<TransportProtocol>,
/// Optional domain patterns to match (default: all domains)
#[serde(skip_serializing_if = "Option::is_none")]
pub domains: Option<DomainSpec>,
@@ -115,7 +129,7 @@ pub struct RouteMatch {
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<HashMap<String, String>>,
/// Match specific protocol: "http" (includes h2 + websocket) or "tcp"
/// Match specific protocol: "http", "tcp", "udp", "quic", "http3"
#[serde(skip_serializing_if = "Option::is_none")]
pub protocol: Option<String>,
}
@@ -367,9 +381,19 @@ pub struct NfTablesOptions {
pub enum BackendProtocol {
Http1,
Http2,
Http3,
Auto,
}
/// Transport protocol for route matching.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TransportProtocol {
Tcp,
Udp,
All,
}
/// Action options.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -470,6 +494,10 @@ pub struct RouteTarget {
#[serde(skip_serializing_if = "Option::is_none")]
pub advanced: Option<RouteAdvanced>,
/// Override transport for backend connection (e.g., receive QUIC but forward as TCP)
#[serde(skip_serializing_if = "Option::is_none")]
pub backend_transport: Option<TransportProtocol>,
/// Priority for matching (higher values checked first, default: 0)
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<i32>,
@@ -524,6 +552,68 @@ pub struct RouteAction {
/// PROXY protocol support (default for all targets)
#[serde(skip_serializing_if = "Option::is_none")]
pub send_proxy_protocol: Option<bool>,
/// UDP-specific settings (session tracking, datagram limits, QUIC config)
#[serde(skip_serializing_if = "Option::is_none")]
pub udp: Option<RouteUdp>,
}
// ─── UDP & QUIC Config ──────────────────────────────────────────────
/// UDP-specific settings for route actions.
/// Matches TypeScript: `IRouteUdp`
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RouteUdp {
/// Idle timeout for a UDP session/flow in ms. Default: 60000
#[serde(skip_serializing_if = "Option::is_none")]
pub session_timeout: Option<u64>,
/// Max concurrent UDP sessions per source IP. Default: 1000
#[serde(skip_serializing_if = "Option::is_none")]
pub max_sessions_per_ip: Option<u32>,
/// Max accepted datagram size in bytes. Default: 65535
#[serde(skip_serializing_if = "Option::is_none")]
pub max_datagram_size: Option<u32>,
/// QUIC-specific configuration
#[serde(skip_serializing_if = "Option::is_none")]
pub quic: Option<RouteQuic>,
}
/// QUIC and HTTP/3 settings.
/// Matches TypeScript: `IRouteQuic`
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RouteQuic {
/// QUIC connection idle timeout in ms. Default: 30000
#[serde(skip_serializing_if = "Option::is_none")]
pub max_idle_timeout: Option<u64>,
/// Max concurrent bidirectional streams per QUIC connection. Default: 100
#[serde(skip_serializing_if = "Option::is_none")]
pub max_concurrent_bidi_streams: Option<u32>,
/// Max concurrent unidirectional streams per QUIC connection. Default: 100
#[serde(skip_serializing_if = "Option::is_none")]
pub max_concurrent_uni_streams: Option<u32>,
/// Enable HTTP/3 over this QUIC endpoint. Default: false
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_http3: Option<bool>,
/// Port to advertise in Alt-Svc header on TCP HTTP responses
#[serde(skip_serializing_if = "Option::is_none")]
pub alt_svc_port: Option<u16>,
/// Max age for Alt-Svc advertisement in seconds. Default: 86400
#[serde(skip_serializing_if = "Option::is_none")]
pub alt_svc_max_age: Option<u64>,
/// Initial congestion window size in bytes
#[serde(skip_serializing_if = "Option::is_none")]
pub initial_congestion_window: Option<u32>,
}
// ─── Route Config ────────────────────────────────────────────────────