use std::io::{ErrorKind, Read, Write};
use std::net::{TcpListener, TcpStream, UdpSocket};
use std::sync::Arc;
use std::thread::JoinHandle;
use std::time::{Duration, Instant};
use foundation_core::synca::OnSignal;
const BACKEND_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5);
struct HalfRelay {
pending: Vec<u8>,
written: usize,
flushing: bool,
}
enum Pump {
Progressed,
Idle,
Closed,
}
impl HalfRelay {
fn new() -> Self {
Self { pending: Vec::new(), written: 0, flushing: false }
}
fn pump<S: Read, D: Write>(&mut self, src: &mut S, dst: &mut D, buf: &mut [u8]) -> Pump {
if self.flushing {
match dst.flush() {
Ok(()) => self.flushing = false,
Err(ref e) if e.kind() == ErrorKind::WouldBlock => return Pump::Idle,
Err(_) => return Pump::Closed,
}
}
if self.written < self.pending.len() {
match dst.write(&self.pending[self.written..]) {
Ok(0) => return Pump::Closed,
Ok(n) => {
self.written += n;
if self.written >= self.pending.len() {
self.pending.clear();
self.written = 0;
match dst.flush() {
Ok(()) => {}
Err(ref e) if e.kind() == ErrorKind::WouldBlock => self.flushing = true,
Err(_) => return Pump::Closed,
}
}
return Pump::Progressed;
}
Err(ref e) if e.kind() == ErrorKind::WouldBlock => return Pump::Idle,
Err(_) => return Pump::Closed,
}
}
match src.read(buf) {
Ok(0) => Pump::Closed,
Ok(n) => {
self.pending.extend_from_slice(&buf[..n]);
Pump::Progressed
}
Err(ref e) if e.kind() == ErrorKind::WouldBlock => Pump::Idle,
Err(_) => Pump::Closed,
}
}
}
pub fn splice_bidirectional<A: Read + Write, B: Read + Write>(mut a: A, mut b: B) {
let mut buf = [0u8; 16 * 1024];
let mut a_to_b = HalfRelay::new();
let mut b_to_a = HalfRelay::new();
loop {
let ab = a_to_b.pump(&mut a, &mut b, &mut buf);
let ba = b_to_a.pump(&mut b, &mut a, &mut buf);
if matches!(ab, Pump::Closed) || matches!(ba, Pump::Closed) {
break;
}
if matches!(ab, Pump::Idle) && matches!(ba, Pump::Idle) {
std::thread::sleep(Duration::from_millis(1));
}
}
}
#[derive(Debug)]
pub struct TcpPassthrough {
local_addr: std::net::SocketAddr,
shutdown: Arc<OnSignal>,
accept_thread: Option<JoinHandle<()>>,
}
impl TcpPassthrough {
pub fn start(listen_addr: &str, backend_authority: &str) -> std::io::Result<Self> {
let listener = TcpListener::bind(listen_addr)?;
listener.set_nonblocking(true)?;
let local_addr = listener.local_addr()?;
let shutdown = Arc::new(OnSignal::new());
let backend = backend_authority.to_string();
let loop_shutdown = Arc::clone(&shutdown);
let accept_thread = std::thread::spawn(move || {
accept_loop(&listener, &backend, &loop_shutdown);
});
Ok(Self {
local_addr,
shutdown,
accept_thread: Some(accept_thread),
})
}
#[must_use]
pub fn local_addr(&self) -> std::net::SocketAddr {
self.local_addr
}
pub fn shutdown(mut self) {
self.shutdown.turn_on();
if let Some(handle) = self.accept_thread.take() {
let _ = handle.join();
}
}
}
impl Drop for TcpPassthrough {
fn drop(&mut self) {
self.shutdown.turn_on();
if let Some(handle) = self.accept_thread.take() {
let _ = handle.join();
}
}
}
fn accept_loop(listener: &TcpListener, backend_authority: &str, shutdown: &Arc<OnSignal>) {
loop {
if shutdown.probe() {
return;
}
match listener.accept() {
Ok((client, _addr)) => {
let backend = backend_authority.to_string();
std::thread::spawn(move || {
if let Err(e) = splice_client_to_backend(client, &backend) {
tracing::warn!(%backend, "tcp passthrough failed: {e}");
}
});
}
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
std::thread::sleep(Duration::from_millis(5));
}
Err(e) => {
tracing::error!("tcp passthrough accept error: {e}");
std::thread::sleep(Duration::from_millis(20));
}
}
}
}
fn splice_client_to_backend(client: TcpStream, backend_authority: &str) -> std::io::Result<()> {
let backend = TcpStream::connect(backend_authority)?;
client.set_nonblocking(true)?;
backend.set_nonblocking(true)?;
splice_bidirectional(client, backend);
Ok(())
}
#[derive(Debug)]
pub struct UdpPassthrough {
local_addr: std::net::SocketAddr,
shutdown: Arc<OnSignal>,
relay_thread: Option<JoinHandle<()>>,
}
impl UdpPassthrough {
pub fn start(listen_addr: &str, backend_authority: &str) -> std::io::Result<Self> {
let socket = UdpSocket::bind(listen_addr)?;
socket.set_nonblocking(true)?;
let local_addr = socket.local_addr()?;
let shutdown = Arc::new(OnSignal::new());
let backend = backend_authority.to_string();
let loop_shutdown = Arc::clone(&shutdown);
let relay_thread = std::thread::spawn(move || {
relay_udp(socket, &backend, &loop_shutdown);
});
Ok(Self {
local_addr,
shutdown,
relay_thread: Some(relay_thread),
})
}
#[must_use]
pub fn local_addr(&self) -> std::net::SocketAddr {
self.local_addr
}
pub fn shutdown(mut self) {
self.shutdown.turn_on();
if let Some(handle) = self.relay_thread.take() {
let _ = handle.join();
}
}
}
impl Drop for UdpPassthrough {
fn drop(&mut self) {
self.shutdown.turn_on();
if let Some(handle) = self.relay_thread.take() {
let _ = handle.join();
}
}
}
fn relay_udp(socket: UdpSocket, backend_addr: &str, shutdown: &Arc<OnSignal>) {
let mut buf = [0u8; 65535];
let backend = match backend_addr.to_socket_addrs_first() {
Some(addr) => addr,
None => {
tracing::error!(%backend_addr, "UDP relay: cannot resolve backend address");
return;
}
};
loop {
if shutdown.probe() {
return;
}
match socket.recv_from(&mut buf) {
Ok((n, client_addr)) => {
if let Err(e) = socket.send_to(&buf[..n], backend) {
tracing::warn!(%backend, "UDP relay send to backend failed: {e}");
continue;
}
let deadline = Instant::now() + BACKEND_RESPONSE_TIMEOUT;
let mut responded = false;
while Instant::now() < deadline {
if shutdown.probe() {
return;
}
match socket.recv_from(&mut buf) {
Ok((m, _from)) => {
if let Err(e) = socket.send_to(&buf[..m], client_addr) {
tracing::warn!(%client_addr, "UDP relay send to client failed: {e}");
}
responded = true;
break;
}
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
std::thread::sleep(Duration::from_millis(5));
}
Err(e) => {
tracing::warn!("UDP relay recv from backend failed: {e}");
break;
}
}
}
if !responded {
tracing::trace!("UDP relay: no response from backend within {:?}", BACKEND_RESPONSE_TIMEOUT);
}
}
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {}
Err(e) => {
tracing::error!("UDP relay recv error: {e}");
break;
}
}
}
}
trait FirstSocketAddr {
fn to_socket_addrs_first(&self) -> Option<std::net::SocketAddr>;
}
impl FirstSocketAddr for str {
fn to_socket_addrs_first(&self) -> Option<std::net::SocketAddr> {
use std::net::ToSocketAddrs;
self.to_socket_addrs().ok().and_then(|mut it| it.next())
}
}