feat(rustproxy-passthrough): add selective connection recycling for route, security, and certificate updates
This commit is contained in:
@@ -356,12 +356,17 @@ impl RustProxy {
|
||||
|
||||
// Bind UDP ports (if any)
|
||||
if !udp_ports.is_empty() {
|
||||
let conn_tracker = self.listener_manager.as_ref().unwrap().conn_tracker().clone();
|
||||
let tcp_mgr = self.listener_manager.as_ref().unwrap();
|
||||
let conn_tracker = tcp_mgr.conn_tracker().clone();
|
||||
let route_cancels = tcp_mgr.route_cancels().clone();
|
||||
let connection_registry = tcp_mgr.connection_registry().clone();
|
||||
let mut udp_mgr = UdpListenerManager::new(
|
||||
Arc::clone(&*self.route_table.load()),
|
||||
Arc::clone(&self.metrics),
|
||||
conn_tracker,
|
||||
self.cancel_token.clone(),
|
||||
route_cancels,
|
||||
connection_registry,
|
||||
);
|
||||
udp_mgr.set_proxy_ips(udp_proxy_ips.clone());
|
||||
|
||||
@@ -707,6 +712,9 @@ impl RustProxy {
|
||||
.collect();
|
||||
self.metrics.retain_backends(&active_backends);
|
||||
|
||||
// Capture old route manager for diff-based connection recycling
|
||||
let old_manager = self.route_table.load_full();
|
||||
|
||||
// Atomically swap the route table
|
||||
let new_manager = Arc::new(new_manager);
|
||||
self.route_table.store(Arc::clone(&new_manager));
|
||||
@@ -742,9 +750,47 @@ impl RustProxy {
|
||||
listener.update_route_manager(Arc::clone(&new_manager));
|
||||
// Cancel connections on routes that were removed or disabled
|
||||
listener.invalidate_removed_routes(&active_route_ids);
|
||||
// Clean up registry entries for removed routes
|
||||
listener.connection_registry().cleanup_removed_routes(&active_route_ids);
|
||||
// Prune HTTP proxy caches (rate limiters, regex cache, round-robin counters)
|
||||
listener.prune_http_proxy_caches(&active_route_ids);
|
||||
|
||||
// Diff-based connection recycling for changed routes
|
||||
{
|
||||
let registry = listener.connection_registry();
|
||||
for new_route in &routes {
|
||||
let new_id = match &new_route.id {
|
||||
Some(id) => id.as_str(),
|
||||
None => continue,
|
||||
};
|
||||
// Find corresponding old route
|
||||
let old_route = old_manager.routes().iter().find(|r| {
|
||||
r.id.as_deref() == Some(new_id)
|
||||
});
|
||||
let old_route = match old_route {
|
||||
Some(r) => r,
|
||||
None => continue, // new route, no existing connections to recycle
|
||||
};
|
||||
|
||||
// Security diff: re-evaluate existing connections' IPs
|
||||
let old_sec = serde_json::to_string(&old_route.security).ok();
|
||||
let new_sec = serde_json::to_string(&new_route.security).ok();
|
||||
if old_sec != new_sec {
|
||||
if let Some(ref security) = new_route.security {
|
||||
registry.recycle_for_security_change(new_id, security);
|
||||
}
|
||||
// If security removed entirely (became more permissive), no recycling needed
|
||||
}
|
||||
|
||||
// Action diff (targets, TLS mode, etc.): recycle all connections on route
|
||||
let old_action = serde_json::to_string(&old_route.action).ok();
|
||||
let new_action = serde_json::to_string(&new_route.action).ok();
|
||||
if old_action != new_action {
|
||||
registry.recycle_for_route_change(new_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add new ports
|
||||
for port in &new_ports {
|
||||
if !old_ports.contains(port) {
|
||||
@@ -787,12 +833,16 @@ impl RustProxy {
|
||||
if self.udp_listener_manager.is_none() {
|
||||
if let Some(ref listener) = self.listener_manager {
|
||||
let conn_tracker = listener.conn_tracker().clone();
|
||||
let route_cancels = listener.route_cancels().clone();
|
||||
let connection_registry = listener.connection_registry().clone();
|
||||
let conn_config = Self::build_connection_config(&self.options);
|
||||
let mut udp_mgr = UdpListenerManager::new(
|
||||
Arc::clone(&new_manager),
|
||||
Arc::clone(&self.metrics),
|
||||
conn_tracker,
|
||||
self.cancel_token.clone(),
|
||||
route_cancels,
|
||||
connection_registry,
|
||||
);
|
||||
udp_mgr.set_proxy_ips(conn_config.proxy_ips);
|
||||
self.udp_listener_manager = Some(udp_mgr);
|
||||
@@ -1096,6 +1146,10 @@ impl RustProxy {
|
||||
}
|
||||
|
||||
/// Load a certificate for a domain and hot-swap the TLS configuration.
|
||||
///
|
||||
/// If the cert PEM differs from the currently loaded cert for this domain,
|
||||
/// existing connections for the domain are gracefully recycled (GOAWAY for
|
||||
/// HTTP/2, Connection: close for HTTP/1.1, graceful FIN for TCP).
|
||||
pub async fn load_certificate(
|
||||
&mut self,
|
||||
domain: &str,
|
||||
@@ -1105,6 +1159,12 @@ impl RustProxy {
|
||||
) -> Result<()> {
|
||||
info!("Loading certificate for domain: {}", domain);
|
||||
|
||||
// Check if the cert actually changed (for selective connection recycling)
|
||||
let cert_changed = self.loaded_certs
|
||||
.get(domain)
|
||||
.map(|existing| existing.cert_pem != cert_pem)
|
||||
.unwrap_or(false); // new domain = no existing connections to recycle
|
||||
|
||||
// Store in cert manager if available
|
||||
if let Some(ref cm_arc) = self.cert_manager {
|
||||
let now = std::time::SystemTime::now()
|
||||
@@ -1153,6 +1213,13 @@ impl RustProxy {
|
||||
}
|
||||
}
|
||||
|
||||
// Recycle existing connections if cert actually changed
|
||||
if cert_changed {
|
||||
if let Some(ref listener) = self.listener_manager {
|
||||
listener.connection_registry().recycle_for_cert_change(domain);
|
||||
}
|
||||
}
|
||||
|
||||
info!("Certificate loaded and TLS config updated for {}", domain);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user