Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0475e6b442 | |||
| 8cdb95a853 |
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user