fix(rustproxy): install default rustls crypto provider early; detect and skip raw fast-path for HTTP connections and return proper HTTP 502 when no route matches
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-02-12 - 23.1.3 - fix(rustproxy)
|
||||
install default rustls crypto provider early; detect and skip raw fast-path for HTTP connections and return proper HTTP 502 when no route matches
|
||||
|
||||
- Install ring-based rustls crypto provider at startup to prevent panics from instant-acme/hyper-rustls calling ClientConfig::builder() before TLS listeners are initialized
|
||||
- Add a non-blocking 10ms peek to detect HTTP traffic in the TCP passthrough fast-path to avoid misrouting HTTP and ensure HTTP proxy handles CORS, errors, and request-level routing
|
||||
- Skip the fast-path and fall back to the HTTP proxy when HTTP is detected (with a debug log)
|
||||
- When no route matches for detected HTTP connections, send an HTTP 502 Bad Gateway response and close the connection instead of silently dropping it
|
||||
|
||||
## 2026-02-11 - 23.1.2 - fix(core)
|
||||
use node: scoped builtin imports and add route unit tests
|
||||
|
||||
|
||||
@@ -364,6 +364,10 @@ impl TcpListenerManager {
|
||||
// doesn't send initial data (e.g., SMTP, greeting-based protocols).
|
||||
// If a route matches by port alone and doesn't need domain/path/TLS info,
|
||||
// we can forward immediately without waiting for client data.
|
||||
//
|
||||
// IMPORTANT: HTTP connections must NOT use this path — they need the HTTP
|
||||
// proxy for proper error responses, CORS handling, and request-level routing.
|
||||
// We detect HTTP via a non-blocking peek before committing to raw forwarding.
|
||||
{
|
||||
let quick_ctx = rustproxy_routing::MatchContext {
|
||||
port,
|
||||
@@ -384,7 +388,28 @@ impl TcpListenerManager {
|
||||
|
||||
// Only use fast path for simple port-only forward routes with no TLS
|
||||
if has_no_domain && has_no_path && is_forward && has_no_tls {
|
||||
if let Some(target) = quick_match.target {
|
||||
// Non-blocking peek: if client has already sent data that looks
|
||||
// like HTTP, skip fast path and let the normal path handle it
|
||||
// through the HTTP proxy (for CORS, error responses, path routing).
|
||||
let is_likely_http = {
|
||||
let mut probe = [0u8; 16];
|
||||
// Brief peek: HTTP clients send data immediately after connect.
|
||||
// Server-speaks-first protocols (SMTP etc.) send nothing initially.
|
||||
// 10ms is ample for any HTTP client while negligible for
|
||||
// server-speaks-first protocols (which wait seconds for greeting).
|
||||
match tokio::time::timeout(
|
||||
std::time::Duration::from_millis(10),
|
||||
stream.peek(&mut probe),
|
||||
).await {
|
||||
Ok(Ok(n)) if n > 0 => sni_parser::is_http(&probe[..n]),
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
|
||||
if is_likely_http {
|
||||
debug!("Fast-path skipped: HTTP detected from {}, using HTTP proxy", peer_addr);
|
||||
// Fall through to normal path for HTTP proxy handling
|
||||
} else if let Some(target) = quick_match.target {
|
||||
let target_host = target.host.first().to_string();
|
||||
let target_port = target.port.resolve(port);
|
||||
let route_id = quick_match.route.id.as_deref();
|
||||
@@ -562,6 +587,17 @@ impl TcpListenerManager {
|
||||
Some(rm) => rm,
|
||||
None => {
|
||||
debug!("No route matched for port {} domain {:?}", port, domain);
|
||||
if is_http {
|
||||
// Send a proper HTTP error instead of dropping the connection
|
||||
use tokio::io::AsyncWriteExt;
|
||||
let body = "No route matched";
|
||||
let resp = format!(
|
||||
"HTTP/1.1 502 Bad Gateway\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
|
||||
body.len(), body
|
||||
);
|
||||
let _ = stream.write_all(resp.as_bytes()).await;
|
||||
let _ = stream.shutdown().await;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,6 +29,11 @@ struct Cli {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// Install the default CryptoProvider early, before any TLS or ACME code runs.
|
||||
// This prevents panics from instant-acme/hyper-rustls calling ClientConfig::builder()
|
||||
// before TLS listeners have started. Idempotent — later calls harmlessly return Err.
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
// Initialize tracing - write to stderr so stdout is reserved for management IPC
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartproxy',
|
||||
version: '23.1.2',
|
||||
version: '23.1.3',
|
||||
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