Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0475e6b442 | |||
| 8cdb95a853 |
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# 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)
|
## 2026-03-16 - 25.11.6 - fix(rustproxy-http,rustproxy-passthrough)
|
||||||
improve upstream connection cleanup and graceful tunnel shutdown
|
improve upstream connection cleanup and graceful tunnel shutdown
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartproxy",
|
"name": "@push.rocks/smartproxy",
|
||||||
"version": "25.11.6",
|
"version": "25.11.7",
|
||||||
"private": false,
|
"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.",
|
"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",
|
"main": "dist_ts/index.js",
|
||||||
|
|||||||
@@ -472,12 +472,12 @@ impl TcpListenerManager {
|
|||||||
let permit = match conn_semaphore.clone().try_acquire_owned() {
|
let permit = match conn_semaphore.clone().try_acquire_owned() {
|
||||||
Ok(permit) => permit,
|
Ok(permit) => permit,
|
||||||
Err(tokio::sync::TryAcquireError::NoPermits) => {
|
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);
|
drop(stream);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Err(tokio::sync::TryAcquireError::Closed) => {
|
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);
|
drop(stream);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -485,7 +485,7 @@ impl TcpListenerManager {
|
|||||||
|
|
||||||
// Check per-IP limits and rate limiting
|
// Check per-IP limits and rate limiting
|
||||||
if !conn_tracker.try_accept(&ip) {
|
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(stream);
|
||||||
drop(permit);
|
drop(permit);
|
||||||
continue;
|
continue;
|
||||||
@@ -517,7 +517,7 @@ impl TcpListenerManager {
|
|||||||
stream, port, peer_addr, rm, m, tc, sa, hp, cc, cn, sr, rc,
|
stream, port, peer_addr, rm, m, tc, sa, hp, cc, cn, sr, rc,
|
||||||
).await;
|
).await;
|
||||||
if let Err(e) = result {
|
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(
|
if !rustproxy_http::request_filter::RequestFilter::check_ip_security(
|
||||||
security, &peer_addr.ip(),
|
security, &peer_addr.ip(),
|
||||||
) {
|
) {
|
||||||
debug!("Connection from {} blocked by route security", peer_addr);
|
warn!("Connection from {} blocked by route security", peer_addr);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -808,7 +808,7 @@ impl TcpListenerManager {
|
|||||||
let route_match = match route_match {
|
let route_match = match route_match {
|
||||||
Some(rm) => rm,
|
Some(rm) => rm,
|
||||||
None => {
|
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 {
|
if is_http {
|
||||||
// Send a proper HTTP error instead of dropping the connection
|
// Send a proper HTTP error instead of dropping the connection
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
@@ -842,7 +842,7 @@ impl TcpListenerManager {
|
|||||||
security,
|
security,
|
||||||
&peer_addr.ip(),
|
&peer_addr.ip(),
|
||||||
) {
|
) {
|
||||||
debug!("Connection from {} blocked by route security", peer_addr);
|
warn!("Connection from {} blocked by route security", peer_addr);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -985,13 +985,18 @@ impl TcpListenerManager {
|
|||||||
Err(_) => return Err("TLS handshake timeout".into()),
|
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 mut buf_stream = tokio::io::BufReader::new(tls_stream);
|
||||||
let peeked = {
|
let peeked = {
|
||||||
use tokio::io::AsyncBufReadExt;
|
use tokio::io::AsyncBufReadExt;
|
||||||
match buf_stream.fill_buf().await {
|
match tokio::time::timeout(
|
||||||
Ok(data) => sni_parser::is_http(data),
|
std::time::Duration::from_millis(conn_config.initial_data_timeout_ms),
|
||||||
Err(_) => false,
|
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()),
|
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 mut buf_stream = tokio::io::BufReader::new(tls_stream);
|
||||||
let is_http_data = {
|
let is_http_data = {
|
||||||
use tokio::io::AsyncBufReadExt;
|
use tokio::io::AsyncBufReadExt;
|
||||||
match buf_stream.fill_buf().await {
|
match tokio::time::timeout(
|
||||||
Ok(data) => sni_parser::is_http(data),
|
std::time::Duration::from_millis(conn_config.initial_data_timeout_ms),
|
||||||
Err(_) => false,
|
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);
|
let new_manager = Arc::new(new_manager);
|
||||||
self.route_table.store(Arc::clone(&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 {
|
if let Some(ref mut listener) = self.listener_manager {
|
||||||
listener.update_route_manager(Arc::clone(&new_manager));
|
// 1. Update TLS configs first (so new certs are available before new routes)
|
||||||
// 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
|
|
||||||
let mut tls_configs = Self::extract_tls_configs(&routes);
|
let mut tls_configs = Self::extract_tls_configs(&routes);
|
||||||
if let Some(ref cm_arc) = self.cert_manager {
|
if let Some(ref cm_arc) = self.cert_manager {
|
||||||
let cm = cm_arc.lock().await;
|
let cm = cm_arc.lock().await;
|
||||||
@@ -661,6 +659,13 @@ impl RustProxy {
|
|||||||
}
|
}
|
||||||
listener.set_tls_configs(tls_configs);
|
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
|
// Add new ports
|
||||||
for port in &new_ports {
|
for port in &new_ports {
|
||||||
if !old_ports.contains(port) {
|
if !old_ports.contains(port) {
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
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.'
|
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