fix: wire channel proxy into handler, add client reconnection with backoff, fix ADR-006 violations
- handler.channel_open_direct_tcpip now proxies non-wraith channels via connect_outbound+proxy_channel instead of dropping them - ClientSession.run() spawns reconnect monitor that detects handle closure, reconnects with exponential backoff (1s/2s/4s/8s/16s/30s cap), and re-registers remote port forwards - Remove server-side logging of tunnel destinations (ADR-006 compliance) - Remove debug-level logging of proxy targets in channel_proxy
This commit is contained in:
@@ -306,6 +306,60 @@ impl<T: Transport> ClientSession<T> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut wait_shutdown = self.shutdown_rx.clone();
|
let mut wait_shutdown = self.shutdown_rx.clone();
|
||||||
|
let reconnect_handle = Arc::clone(&self.handle);
|
||||||
|
let reconnect_transport = Arc::clone(&self.transport);
|
||||||
|
let reconnect_auth = Arc::clone(&self.auth_config);
|
||||||
|
let reconnect_username = self.username.clone();
|
||||||
|
let reconnect_shutdown = self.shutdown_rx.clone();
|
||||||
|
let reconnect_remote_specs = remote_specs.clone();
|
||||||
|
|
||||||
|
let reconnect_monitor = tokio::spawn(async move {
|
||||||
|
let mut attempts: u32 = 0;
|
||||||
|
loop {
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
if *reconnect_shutdown.borrow() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let h = reconnect_handle.lock().await;
|
||||||
|
if h.is_closed() {
|
||||||
|
drop(h);
|
||||||
|
info!("SSH session closed, starting reconnection");
|
||||||
|
let backoff = backoff_duration(attempts);
|
||||||
|
warn!("reconnect attempt #{}, waiting {:?}", attempts + 1, backoff);
|
||||||
|
tokio::time::sleep(backoff).await;
|
||||||
|
|
||||||
|
let handler = ClientHandler::from_config(&reconnect_auth);
|
||||||
|
let username = reconnect_username.clone();
|
||||||
|
match establish_session(&*reconnect_transport, handler, &reconnect_auth, &username).await {
|
||||||
|
Ok(new_handle) => {
|
||||||
|
info!("reconnection successful");
|
||||||
|
{
|
||||||
|
let mut guard = reconnect_handle.lock().await;
|
||||||
|
*guard = new_handle;
|
||||||
|
}
|
||||||
|
for spec in &reconnect_remote_specs {
|
||||||
|
match RemoteForwarder::new(spec.clone()) {
|
||||||
|
Ok(rf) => {
|
||||||
|
let mut h = reconnect_handle.lock().await;
|
||||||
|
match rf.register(&mut h).await {
|
||||||
|
Ok(_) => debug!("re-registered remote forward: {}", spec),
|
||||||
|
Err(e) => warn!("failed to re-register remote forward {}: {e}", spec),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => warn!("failed to create remote forwarder: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attempts = 0;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("reconnection attempt failed: {e}");
|
||||||
|
attempts += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = wait_shutdown.changed() => {
|
_ = wait_shutdown.changed() => {
|
||||||
if *wait_shutdown.borrow() {
|
if *wait_shutdown.borrow() {
|
||||||
@@ -317,6 +371,8 @@ impl<T: Transport> ClientSession<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reconnect_monitor.abort();
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
signal_done.abort();
|
signal_done.abort();
|
||||||
|
|
||||||
@@ -358,6 +414,48 @@ fn derive_username() -> String {
|
|||||||
.unwrap_or_else(|_| "wraith".to_string())
|
.unwrap_or_else(|_| "wraith".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn establish_session<T: Transport>(
|
||||||
|
transport: &T,
|
||||||
|
handler: ClientHandler,
|
||||||
|
auth_config: &ClientAuthConfig,
|
||||||
|
username: &str,
|
||||||
|
) -> Result<client::Handle<ClientHandler>, ConnectError> {
|
||||||
|
let stream = transport.connect().await.map_err(|e| {
|
||||||
|
error!("transport connect failed: {e}");
|
||||||
|
ConnectError::ConnectionFailed
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let config = Arc::new(client::Config::default());
|
||||||
|
let mut handle = client::connect_stream(config, stream, handler)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("SSH connect failed: {e}");
|
||||||
|
ConnectError::ConnectionFailed
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let auth_ok = auth_config
|
||||||
|
.authenticate(&mut handle, username)
|
||||||
|
.await
|
||||||
|
.map_err(|_| ConnectError::AuthFailed)?;
|
||||||
|
if !auth_ok {
|
||||||
|
return Err(ConnectError::AuthFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn backoff_duration(attempt: u32) -> Duration {
|
||||||
|
let secs: u64 = match attempt {
|
||||||
|
0 => 1,
|
||||||
|
1 => 2,
|
||||||
|
2 => 4,
|
||||||
|
3 => 8,
|
||||||
|
4 => 16,
|
||||||
|
_ => 30,
|
||||||
|
};
|
||||||
|
Duration::from_secs(secs)
|
||||||
|
}
|
||||||
|
|
||||||
fn build_local_forwarders(opts: &ConnectOptions) -> Result<Vec<LocalForwarder>, ConnectError> {
|
fn build_local_forwarders(opts: &ConnectOptions) -> Result<Vec<LocalForwarder>, ConnectError> {
|
||||||
let mut forwarders = Vec::new();
|
let mut forwarders = Vec::new();
|
||||||
for spec_str in &opts.forwards {
|
for spec_str in &opts.forwards {
|
||||||
|
|||||||
@@ -142,16 +142,13 @@ async fn connect_http_connect(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_connection_error(e: std::io::Error, target: SocketAddr) -> ChannelProxyError {
|
fn map_connection_error(e: std::io::Error, _target: SocketAddr) -> ChannelProxyError {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
std::io::ErrorKind::ConnectionRefused => ChannelProxyError::ConnectionRefused,
|
std::io::ErrorKind::ConnectionRefused => ChannelProxyError::ConnectionRefused,
|
||||||
std::io::ErrorKind::AddrNotAvailable
|
std::io::ErrorKind::AddrNotAvailable
|
||||||
| std::io::ErrorKind::NetworkUnreachable
|
| std::io::ErrorKind::NetworkUnreachable
|
||||||
| std::io::ErrorKind::HostUnreachable => ChannelProxyError::TargetUnreachable,
|
| std::io::ErrorKind::HostUnreachable => ChannelProxyError::TargetUnreachable,
|
||||||
_ => {
|
_ => ChannelProxyError::Io(e),
|
||||||
tracing::debug!(error = %e, "outbound connection failed to {:?}", target);
|
|
||||||
ChannelProxyError::Io(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -210,17 +210,7 @@ impl Handler for ServerHandler {
|
|||||||
_session: &mut Session,
|
_session: &mut Session,
|
||||||
) -> Result<bool, Self::Error> {
|
) -> Result<bool, Self::Error> {
|
||||||
if host_to_connect.starts_with(WRAITH_PREFIX) {
|
if host_to_connect.starts_with(WRAITH_PREFIX) {
|
||||||
tracing::info!(
|
|
||||||
host = host_to_connect,
|
|
||||||
port = port_to_connect,
|
|
||||||
"routing to internal control channel handler"
|
|
||||||
);
|
|
||||||
|
|
||||||
if !self.control_channel_router.has_handler() {
|
if !self.control_channel_router.has_handler() {
|
||||||
tracing::warn!(
|
|
||||||
host = host_to_connect,
|
|
||||||
"no control channel handler configured, rejecting channel open"
|
|
||||||
);
|
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,8 +218,28 @@ impl Handler for ServerHandler {
|
|||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = (host_to_connect, port_to_connect, originator_address, originator_port, channel);
|
let target_host = host_to_connect.to_string();
|
||||||
Ok(false)
|
let target_port = port_to_connect;
|
||||||
|
let proxy_config = self.outbound_proxy.clone().unwrap_or(ProxyConfig {
|
||||||
|
mode: ProxyMode::Direct,
|
||||||
|
});
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let target = match format!("{target_host}:{target_port}").parse::<std::net::SocketAddr>() {
|
||||||
|
Ok(addr) => addr,
|
||||||
|
Err(_) => match tokio::net::lookup_host((&target_host[..], target_port as u16)).await {
|
||||||
|
Ok(mut addrs) => match addrs.next() {
|
||||||
|
Some(addr) => addr,
|
||||||
|
None => return,
|
||||||
|
},
|
||||||
|
Err(_) => return,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
crate::server::channel_proxy::proxy_channel(channel.into_stream(), target, &proxy_config).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
let _ = (originator_address, originator_port);
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn channel_open_session(
|
async fn channel_open_session(
|
||||||
|
|||||||
Reference in New Issue
Block a user