Compare commits

...

2 Commits

Author SHA1 Message Date
0475e6b442 v25.11.7
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-16 03:01:16 +00:00
8cdb95a853 fix(rustproxy): prevent TLS route reload certificate mismatches and tighten passthrough connection handling 2026-03-16 03:01:16 +00:00
5 changed files with 47 additions and 25 deletions

View File

@@ -1,5 +1,12 @@
# Changelog
## 2026-03-16 - 25.11.7 - fix(rustproxy)
prevent TLS route reload certificate mismatches and tighten passthrough connection handling
- Load updated TLS configs before swapping the route manager so newly visible routes always have their certificates available.
- Add timeouts when peeking initial decrypted data after TLS handshake to avoid leaked idle connections.
- Raise dropped, blocked, unmatched, and errored passthrough connection events from debug to warn for better operational visibility.
## 2026-03-16 - 25.11.6 - fix(rustproxy-http,rustproxy-passthrough)
improve upstream connection cleanup and graceful tunnel shutdown

View File

@@ -1,6 +1,6 @@
{
"name": "@push.rocks/smartproxy",
"version": "25.11.6",
"version": "25.11.7",
"private": false,
"description": "A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.",
"main": "dist_ts/index.js",

View File

@@ -472,12 +472,12 @@ impl TcpListenerManager {
let permit = match conn_semaphore.clone().try_acquire_owned() {
Ok(permit) => permit,
Err(tokio::sync::TryAcquireError::NoPermits) => {
debug!("Global connection limit reached, dropping connection from {}", peer_addr);
warn!("Global connection limit reached, dropping connection from {}", peer_addr);
drop(stream);
continue;
}
Err(tokio::sync::TryAcquireError::Closed) => {
debug!("Connection semaphore closed, dropping connection from {}", peer_addr);
warn!("Connection semaphore closed, dropping connection from {}", peer_addr);
drop(stream);
continue;
}
@@ -485,7 +485,7 @@ impl TcpListenerManager {
// Check per-IP limits and rate limiting
if !conn_tracker.try_accept(&ip) {
debug!("Rejected connection from {} (per-IP limit or rate limit)", peer_addr);
warn!("Rejected connection from {} (per-IP limit or rate limit)", peer_addr);
drop(stream);
drop(permit);
continue;
@@ -517,7 +517,7 @@ impl TcpListenerManager {
stream, port, peer_addr, rm, m, tc, sa, hp, cc, cn, sr, rc,
).await;
if let Err(e) = result {
debug!("Connection error from {}: {}", peer_addr, e);
warn!("Connection error from {}: {}", peer_addr, e);
}
});
}
@@ -662,7 +662,7 @@ impl TcpListenerManager {
if !rustproxy_http::request_filter::RequestFilter::check_ip_security(
security, &peer_addr.ip(),
) {
debug!("Connection from {} blocked by route security", peer_addr);
warn!("Connection from {} blocked by route security", peer_addr);
return Ok(());
}
}
@@ -808,7 +808,7 @@ impl TcpListenerManager {
let route_match = match route_match {
Some(rm) => rm,
None => {
debug!("No route matched for port {} domain {:?}", port, domain);
warn!("No route matched for port {} domain {:?} from {}", port, domain, peer_addr);
if is_http {
// Send a proper HTTP error instead of dropping the connection
use tokio::io::AsyncWriteExt;
@@ -842,7 +842,7 @@ impl TcpListenerManager {
security,
&peer_addr.ip(),
) {
debug!("Connection from {} blocked by route security", peer_addr);
warn!("Connection from {} blocked by route security", peer_addr);
return Ok(());
}
}
@@ -985,13 +985,18 @@ impl TcpListenerManager {
Err(_) => return Err("TLS handshake timeout".into()),
};
// Peek at decrypted data to determine if HTTP
// Peek at decrypted data to determine if HTTP.
// Timeout prevents connection leak if client completes TLS
// but never sends application data (scanners, health probes, slow-loris).
let mut buf_stream = tokio::io::BufReader::new(tls_stream);
let peeked = {
use tokio::io::AsyncBufReadExt;
match buf_stream.fill_buf().await {
Ok(data) => sni_parser::is_http(data),
Err(_) => false,
match tokio::time::timeout(
std::time::Duration::from_millis(conn_config.initial_data_timeout_ms),
buf_stream.fill_buf(),
).await {
Ok(Ok(data)) => sni_parser::is_http(data),
Ok(Err(_)) | Err(_) => false,
}
};
@@ -1060,13 +1065,18 @@ impl TcpListenerManager {
Err(_) => return Err("TLS handshake timeout".into()),
};
// Peek at decrypted data to detect protocol
// Peek at decrypted data to detect protocol.
// Timeout prevents connection leak if client completes TLS
// but never sends application data (scanners, health probes, slow-loris).
let mut buf_stream = tokio::io::BufReader::new(tls_stream);
let is_http_data = {
use tokio::io::AsyncBufReadExt;
match buf_stream.fill_buf().await {
Ok(data) => sni_parser::is_http(data),
Err(_) => false,
match tokio::time::timeout(
std::time::Duration::from_millis(conn_config.initial_data_timeout_ms),
buf_stream.fill_buf(),
).await {
Ok(Ok(data)) => sni_parser::is_http(data),
Ok(Err(_)) | Err(_) => false,
}
};

View File

@@ -632,15 +632,13 @@ impl RustProxy {
let new_manager = Arc::new(new_manager);
self.route_table.store(Arc::clone(&new_manager));
// Update listener manager
// Update listener manager.
// IMPORTANT: TLS configs must be swapped BEFORE the route manager so that
// new routes only become visible after their certs are loaded. The reverse
// order (routes first) creates a window where connections match new routes
// but get the old TLS acceptor, causing cert mismatches.
if let Some(ref mut listener) = self.listener_manager {
listener.update_route_manager(Arc::clone(&new_manager));
// Cancel connections on routes that were removed or disabled
listener.invalidate_removed_routes(&active_route_ids);
// Prune HTTP proxy caches (rate limiters, regex cache, round-robin counters)
listener.prune_http_proxy_caches(&active_route_ids);
// Update TLS configs
// 1. Update TLS configs first (so new certs are available before new routes)
let mut tls_configs = Self::extract_tls_configs(&routes);
if let Some(ref cm_arc) = self.cert_manager {
let cm = cm_arc.lock().await;
@@ -661,6 +659,13 @@ impl RustProxy {
}
listener.set_tls_configs(tls_configs);
// 2. Now swap the route manager (new routes become visible with certs already loaded)
listener.update_route_manager(Arc::clone(&new_manager));
// Cancel connections on routes that were removed or disabled
listener.invalidate_removed_routes(&active_route_ids);
// Prune HTTP proxy caches (rate limiters, regex cache, round-robin counters)
listener.prune_http_proxy_caches(&active_route_ids);
// Add new ports
for port in &new_ports {
if !old_ports.contains(port) {

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartproxy',
version: '25.11.6',
version: '25.11.7',
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
}