Files
telemt/src/maestro/runtime_build_tests.rs
T

264 lines
8.6 KiB
Rust

use super::*;
fn test_listener(port: u16) -> crate::config::ListenerConfig {
crate::config::ListenerConfig {
ip: "127.0.0.1".parse().unwrap(),
transport: crate::config::ListenerTransport::Mtproxy,
port: Some(port),
client_mss: None,
synlimit: crate::config::SynLimitMode::Off,
synlimit_seconds: 60,
synlimit_hitcount: 48,
synlimit_burst: 24,
synlimit_ios_seconds: 1,
synlimit_ios_hitcount: 12,
synlimit_ios_burst: 24,
synlimit_hashlimit_expire_ms: 60_000,
synlimit_hashlimit_size: 32_768,
announce: None,
announce_ip: None,
proxy_protocol: None,
reuse_allow: false,
web_client_ip_source: crate::config::WebClientIpSource::XForwardedFor,
web_trusted_proxy_cidrs: Vec::new(),
}
}
#[test]
fn process_socket_and_logging_changes_are_deferred() {
let old = ProxyConfig::default();
let mut new = old.clone();
new.server.listen_backlog = new.server.listen_backlog.saturating_add(1);
new.general.disable_colors = !new.general.disable_colors;
let fields = deferred_process_fields(&old, &new);
assert!(fields.contains(&"server.listeners".to_string()));
assert!(fields.contains(&"general.disable_colors".to_string()));
}
#[test]
fn global_mss_profiles_are_deferred_with_the_listener_socket_group() {
let old = ProxyConfig::default();
let mut desired = old.clone();
desired.server.client_mss = Some("92".to_string());
desired.server.client_mss_bulk = Some("1400".to_string());
let resolved = resolve_reload_config(&old, &desired);
assert_eq!(
resolved.deferred_process_fields,
vec!["server.listeners".to_string()]
);
assert_eq!(resolved.effective.server.client_mss, old.server.client_mss);
assert_eq!(
resolved.effective.server.client_mss_bulk,
old.server.client_mss_bulk
);
assert!(!resolved.runtime_changed);
}
#[test]
fn mixed_reload_retains_process_state_and_applies_runtime_state() {
let old = ProxyConfig::default();
let mut desired = old.clone();
desired.server.client_mss = Some("92".to_string());
desired.censorship.tls_domain = "reload.example".to_string();
let resolved = resolve_reload_config(&old, &desired);
assert_eq!(resolved.effective.server.client_mss, old.server.client_mss);
assert_eq!(
resolved.effective.censorship.tls_domain,
desired.censorship.tls_domain
);
assert!(resolved.runtime_changed);
assert_eq!(
resolved.deferred_process_fields,
vec!["server.listeners".to_string()]
);
}
#[test]
fn listener_announcement_is_runtime_owned_when_bind_identity_is_stable() {
let mut old = ProxyConfig::default();
old.server.listeners.push(crate::config::ListenerConfig {
ip: "0.0.0.0".parse().unwrap(),
transport: crate::config::ListenerTransport::Mtproxy,
port: Some(443),
client_mss: None,
synlimit: crate::config::SynLimitMode::Off,
synlimit_seconds: 60,
synlimit_hitcount: 48,
synlimit_burst: 24,
synlimit_ios_seconds: 1,
synlimit_ios_hitcount: 12,
synlimit_ios_burst: 24,
synlimit_hashlimit_expire_ms: 60_000,
synlimit_hashlimit_size: 32_768,
announce: None,
announce_ip: None,
proxy_protocol: None,
reuse_allow: false,
web_client_ip_source: crate::config::WebClientIpSource::XForwardedFor,
web_trusted_proxy_cidrs: Vec::new(),
});
let mut desired = old.clone();
desired.server.listeners[0].announce = Some("proxy.example".to_string());
let resolved = resolve_reload_config(&old, &desired);
assert!(resolved.deferred_process_fields.is_empty());
assert_eq!(
resolved.effective.server.listeners[0].announce.as_deref(),
Some("proxy.example")
);
assert!(resolved.runtime_changed);
}
#[test]
fn process_field_labels_are_stable_ordered_and_unique() {
let old = ProxyConfig::default();
let mut desired = old.clone();
desired.server.listen_backlog = desired.server.listen_backlog.saturating_add(1);
desired.server.api.enabled = !desired.server.api.enabled;
desired.server.api.runtime_edge_events_capacity = desired
.server
.api
.runtime_edge_events_capacity
.saturating_add(1);
desired.general.disable_colors = !desired.general.disable_colors;
let resolved = resolve_reload_config(&old, &desired);
assert_eq!(
resolved.deferred_process_fields,
vec![
"server.listeners".to_string(),
"server.api.listen".to_string(),
"server.api.runtime_edge_events_capacity".to_string(),
"general.disable_colors".to_string(),
]
);
}
#[test]
fn runtime_only_change_does_not_require_process_rebind() {
let old = ProxyConfig::default();
let mut new = old.clone();
new.censorship.tls_domain = "reload.example".to_string();
assert!(deferred_process_fields(&old, &new).is_empty());
}
#[test]
fn web_allocation_limits_are_deferred_until_restart() {
let mut old = ProxyConfig::default();
old.rebuild_runtime_user_auth().unwrap();
old.rebuild_runtime_web().unwrap();
let mut desired = old.clone();
desired.web.limits.max_sessions_global += 1;
let resolved = resolve_reload_config(&old, &desired);
assert_eq!(
resolved.deferred_process_fields,
vec!["web.limits".to_string()]
);
assert_eq!(
resolved.effective.web.limits.max_sessions_global,
old.web.limits.max_sessions_global
);
assert!(!resolved.runtime_changed);
}
#[test]
fn web_debug_prefix_dependent_on_new_capacity_is_deferred_with_limits() {
let mut old = ProxyConfig::default();
old.rebuild_runtime_user_auth().unwrap();
old.rebuild_runtime_web().unwrap();
let mut desired = old.clone();
desired.web.limits.max_body_bytes = 4 * 1024 * 1024;
desired.web.debug.body_prefix_bytes = 3 * 1024 * 1024;
let resolved = resolve_reload_config(&old, &desired);
assert_eq!(
resolved.deferred_process_fields,
vec!["web.limits".to_string(), "web.debug".to_string()]
);
assert_eq!(
resolved.effective.web.debug.body_prefix_bytes,
old.web.debug.body_prefix_bytes
);
}
#[test]
fn strict_middle_proxy_requires_a_prepared_pool() {
assert!(strict_middle_proxy_unavailable(true, false, false));
assert!(!strict_middle_proxy_unavailable(true, false, true));
assert!(!strict_middle_proxy_unavailable(true, true, false));
assert!(!strict_middle_proxy_unavailable(false, false, false));
}
#[test]
fn endpoint_only_listener_move_is_runtime_rebindable() {
let mut old = ProxyConfig::default();
old.server.listeners = vec![test_listener(443)];
let mut desired = old.clone();
desired.server.listeners[0].port = Some(8443);
let resolved = resolve_reload_config(&old, &desired);
assert!(resolved.deferred_process_fields.is_empty());
assert_eq!(resolved.effective.server.listeners[0].port, Some(8443));
assert!(resolved.runtime_changed);
}
#[test]
fn synlimited_endpoint_move_remains_restart_only() {
let mut old = ProxyConfig::default();
old.server.listeners = vec![test_listener(443)];
old.server.listeners[0].synlimit = crate::config::SynLimitMode::Nftables;
let mut desired = old.clone();
desired.server.listeners[0].port = Some(8443);
let resolved = resolve_reload_config(&old, &desired);
assert_eq!(
resolved.deferred_process_fields,
vec!["server.listeners".to_string()]
);
assert_eq!(resolved.effective.server.listeners[0].port, Some(443));
assert!(!resolved.runtime_changed);
}
#[test]
fn deferred_listener_identity_cannot_create_an_effective_decoy_loop() {
let mut old = ProxyConfig::default();
old.server.listeners = vec![test_listener(18080)];
old.server.listeners[0].transport = crate::config::ListenerTransport::Web;
let mut desired = old.clone();
desired.server.listeners[0].port = Some(18081);
desired.server.listen_backlog = desired.server.listen_backlog.saturating_add(1);
desired.web.vhosts = vec![
serde_json::from_value(serde_json::json!({
"host": "proxy.example",
"public_addr": "203.0.113.10:443",
"decoy": {
"mode": "http_upstream",
"upstream": "http://127.0.0.1:18080"
},
"profiles": []
}))
.unwrap(),
];
assert!(desired.validate_web_decoy_listener_separation().is_ok());
let resolved = resolve_reload_config(&old, &desired);
assert!(
resolved
.effective
.validate_web_decoy_listener_separation()
.is_err()
);
}