1use std::{io, net::SocketAddr};
9use tokio::{
10 io::copy_bidirectional,
11 net::{TcpListener, TcpStream},
12};
13
14pub struct NetherNetProxy {
31 listener: TcpListener,
32 upstream: SocketAddr,
33}
34
35impl NetherNetProxy {
36 pub async fn bind(listen: SocketAddr, upstream: SocketAddr) -> io::Result<Self> {
38 Ok(Self {
39 listener: TcpListener::bind(listen).await?,
40 upstream,
41 })
42 }
43
44 pub fn local_addr(&self) -> io::Result<SocketAddr> {
46 self.listener.local_addr()
47 }
48
49 pub async fn run(&self) -> io::Result<()> {
54 loop {
55 let (client, peer) = self.listener.accept().await?;
56 let upstream = self.upstream;
57 tokio::spawn(async move {
58 if let Err(error) = forward(client, upstream).await {
59 crate::raknet_log_error!(
60 "NetherNet signaling relay for {peer} failed: {error}"
61 );
62 }
63 });
64 }
65 }
66}
67
68async fn forward(mut client: TcpStream, upstream: SocketAddr) -> io::Result<()> {
69 let mut upstream_stream = TcpStream::connect(upstream).await?;
70 copy_bidirectional(&mut client, &mut upstream_stream).await?;
71 Ok(())
72}
73
74#[cfg(test)]
75mod tests {
76 use super::NetherNetProxy;
77 use std::time::Duration;
78 use tokio::{
79 io::{AsyncReadExt, AsyncWriteExt},
80 net::{TcpListener, TcpStream},
81 time::timeout,
82 };
83
84 #[tokio::test]
85 async fn closes_client_when_upstream_is_unavailable() {
86 let unavailable = TcpListener::bind("127.0.0.1:0").await.unwrap();
87 let upstream_addr = unavailable.local_addr().unwrap();
88 drop(unavailable);
89
90 let proxy = NetherNetProxy::bind("127.0.0.1:0".parse().unwrap(), upstream_addr)
91 .await
92 .unwrap();
93 let proxy_addr = proxy.local_addr().unwrap();
94 let proxy_task = tokio::spawn(async move { proxy.run().await });
95
96 let result = timeout(Duration::from_secs(3), async {
97 let mut client = TcpStream::connect(proxy_addr).await.unwrap();
98 client
99 .write_all(b"GET /v1/join HTTP/1.1\r\n\r\n")
100 .await
101 .unwrap();
102 let mut response = Vec::new();
103 client.read_to_end(&mut response).await.map(|_| response)
104 })
105 .await
106 .expect("client was not closed after the upstream connection failed");
107
108 assert!(result.is_err() || result.as_ref().is_ok_and(Vec::is_empty));
109 proxy_task.abort();
110 let _ = proxy_task.await;
111 }
112
113 #[tokio::test]
114 async fn forwards_signaling_bytes_in_both_directions() {
115 let upstream = TcpListener::bind("127.0.0.1:0").await.unwrap();
116 let upstream_addr = upstream.local_addr().unwrap();
117 let upstream_task = tokio::spawn(async move {
118 let (mut stream, _) = upstream.accept().await.unwrap();
119 let mut request = Vec::new();
120 let mut chunk = [0; 512];
121 loop {
122 let count = stream.read(&mut chunk).await.unwrap();
123 if count == 0 {
124 break;
125 }
126 request.extend_from_slice(&chunk[..count]);
127 if request.windows(4).any(|window| window == b"\r\n\r\n") {
128 break;
129 }
130 }
131 assert!(request.starts_with(b"GET /v1/join HTTP/1.1\r\n"));
132 stream
133 .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok")
134 .await
135 .unwrap();
136 });
137
138 let proxy = NetherNetProxy::bind("127.0.0.1:0".parse().unwrap(), upstream_addr)
139 .await
140 .unwrap();
141 let proxy_addr = proxy.local_addr().unwrap();
142 let proxy_task = tokio::spawn(async move { proxy.run().await });
143
144 let result = timeout(Duration::from_secs(3), async {
145 let mut client = TcpStream::connect(proxy_addr).await.unwrap();
146 client
147 .write_all(b"GET /v1/join HTTP/1.1\r\nHost: localhost\r\n\r\n")
148 .await
149 .unwrap();
150 client.shutdown().await.unwrap();
151 let mut response = Vec::new();
152 client.read_to_end(&mut response).await.unwrap();
153 response
154 })
155 .await
156 .expect("signaling forward timed out");
157
158 assert!(result.starts_with(b"HTTP/1.1 200 OK\r\n"));
159 assert!(result.ends_with(b"\r\nok"));
160 upstream_task.await.unwrap();
161 proxy_task.abort();
162 let _ = proxy_task.await;
163 }
164}