feat: add native dual-stack IPv4/IPv6 support

This commit implements comprehensive dual-stack support for the proxy,
allowing it to listen on both IPv4 and IPv6 addresses simultaneously.

Key changes:
- Added new dual_stack.rs module with DualStackTcpListener implementation
- Updated SSH module to use dual-stack listener
- Updated configuration documentation to reflect IPv6 support
- Added comprehensive tests for dual-stack functionality

The implementation is inspired by tokio_dual_stack but implemented
natively without external dependencies. It provides fair connection
distribution between IPv4 and IPv6 clients while maintaining full
backward compatibility with existing IPv4-only configurations.

Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
This commit is contained in:
2026-03-10 23:38:19 +00:00
parent 41cf6ccc49
commit e16299068f
9 changed files with 396 additions and 4 deletions

View File

@@ -1,13 +1,30 @@
use tokio::io::copy_bidirectional;
use tokio::net::{TcpListener, TcpStream};
use tokio::net::TcpStream;
use crate::dual_stack::DualStackTcpListener;
/// Listens on `listen` and proxies every TCP connection to `backend`.
/// Runs forever; intended to be spawned on a dedicated OS thread + Tokio runtime,
/// matching the pattern used for the cert/ingress watcher.
pub async fn run_tcp_proxy(listen: &str, backend: &str) {
let listener = match TcpListener::bind(listen).await {
// Parse the listen address to determine if it's IPv6 or IPv4
let ipv6_addr = if listen.starts_with('[') {
listen.to_string()
} else {
format!("[::]:{}", listen.split(':').last().unwrap_or("22"))
};
let ipv4_addr = if listen.contains(':') {
// Extract port from the original address
let port = listen.split(':').last().unwrap_or("22");
format!("0.0.0.0:{}", port)
} else {
"0.0.0.0:22".to_string()
};
let listener = match DualStackTcpListener::bind(&ipv6_addr, &ipv4_addr).await {
Ok(l) => {
tracing::info!(%listen, %backend, "SSH TCP proxy listening");
tracing::info!(%listen, %backend, "SSH TCP proxy listening (dual-stack)");
l
}
Err(e) => {