eggress_protocol_reverse/
lib.rs1use std::time::Duration;
2use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt};
3use tokio::net::TcpStream;
4
5pub mod client;
6pub mod compat_pproxy;
7pub mod metrics;
8pub mod server;
9
10pub const HANDSHAKE_ACCEPT: u8 = 0x01;
12
13pub const HANDSHAKE_REJECT: u8 = 0x00;
15
16#[derive(Debug, thiserror::Error)]
18pub enum ProtocolError {
19 #[error("authentication failed")]
20 AuthFailed,
21 #[error("authentication required")]
22 AuthRequired,
23 #[error("connection closed")]
24 ConnectionClosed,
25 #[error("bind address {0} is not in the allow_bind allowlist")]
26 BindDenied(std::net::SocketAddr),
27 #[error("invalid configuration: {0}")]
28 ConfigInvalid(String),
29 #[error("IO error: {0}")]
30 Io(#[from] std::io::Error),
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum ControlState {
36 Disconnected,
37 Connecting,
38 Authenticating,
39 Ready,
40 Draining,
41 Closed,
42}
43
44pub async fn write_auth(
48 stream: &mut TcpStream,
49 username: &str,
50 password: &str,
51) -> Result<(), ProtocolError> {
52 let auth = format!("{}:{}\n", username, password);
53 stream.write_all(auth.as_bytes()).await?;
54 stream.flush().await?;
55 Ok(())
56}
57
58pub async fn read_handshake(stream: &mut TcpStream) -> Result<(), ProtocolError> {
60 let mut buf = [0u8; 1];
61 stream.read_exact(&mut buf).await?;
62 if buf[0] == HANDSHAKE_REJECT {
63 return Err(ProtocolError::AuthFailed);
64 }
65 Ok(())
66}
67
68pub async fn write_handshake_accept(stream: &mut TcpStream) -> Result<(), ProtocolError> {
70 stream.write_all(&[HANDSHAKE_ACCEPT]).await?;
71 Ok(())
72}
73
74pub async fn write_handshake_reject(stream: &mut TcpStream) -> Result<(), ProtocolError> {
76 stream.write_all(&[HANDSHAKE_REJECT]).await?;
77 Ok(())
78}
79
80pub async fn client_auth_handshake(
82 stream: &mut TcpStream,
83 username: &str,
84 password: &str,
85) -> Result<(), ProtocolError> {
86 write_auth(stream, username, password).await?;
87 read_handshake(stream).await
88}
89
90pub async fn server_auth_handshake(
96 stream: &mut TcpStream,
97 expected_user: Option<&str>,
98 expected_pass: Option<&str>,
99) -> Result<String, ProtocolError> {
100 const MAX_AUTH_BYTES: u64 = 4096;
103 let mut auth_buf = Vec::with_capacity(1024);
104 {
105 use tokio::io::AsyncReadExt;
106 let mut limited = (&mut *stream).take(MAX_AUTH_BYTES);
107 let mut reader = tokio::io::BufReader::new(&mut limited);
108 reader.read_until(b'\n', &mut auth_buf).await?;
109 }
110 if auth_buf.is_empty() {
111 return Err(ProtocolError::ConnectionClosed);
112 }
113 if auth_buf.len() > MAX_AUTH_BYTES as usize {
114 return Err(ProtocolError::ConfigInvalid(
115 "auth payload exceeds maximum length".to_string(),
116 ));
117 }
118 if auth_buf.last() != Some(&b'\n') {
121 return Err(ProtocolError::AuthFailed);
122 }
123 auth_buf.pop();
124
125 let auth_str = String::from_utf8_lossy(&auth_buf).to_string();
126
127 if let (Some(exp_user), Some(exp_pass)) = (expected_user, expected_pass) {
129 let (user, pass) = parse_auth_str(&auth_str);
130 use subtle::ConstantTimeEq;
131 let user_ok: bool = user.as_bytes().ct_eq(exp_user.as_bytes()).into();
132 let pass_ok: bool = pass.as_bytes().ct_eq(exp_pass.as_bytes()).into();
133 if !user_ok || !pass_ok {
134 write_handshake_reject(stream).await?;
135 return Err(ProtocolError::AuthFailed);
136 }
137 }
138
139 write_handshake_accept(stream).await?;
140 Ok(redact_auth(&auth_str))
141}
142
143pub fn redact_auth(auth: &str) -> String {
148 let (user, _) = parse_auth_str(auth);
149 if user.is_empty() && auth.is_empty() {
150 return String::new();
151 }
152 if !auth.contains(':') {
153 return "****".to_string();
154 }
155 format!("{}:****", user)
156}
157
158fn parse_auth_str(auth: &str) -> (&str, &str) {
160 match auth.find(':') {
161 Some(idx) => (&auth[..idx], &auth[idx + 1..]),
162 None => (auth, ""),
163 }
164}
165
166pub async fn relay_bidirectional(
170 stream_a: TcpStream,
171 stream_b: TcpStream,
172) -> Result<(), ProtocolError> {
173 relay_bidirectional_with_timeout(stream_a, stream_b, None).await
174}
175
176pub async fn relay_bidirectional_with_timeout(
181 stream_a: TcpStream,
182 stream_b: TcpStream,
183 idle_timeout: Option<Duration>,
184) -> Result<(), ProtocolError> {
185 let (mut a_read, mut a_write) = tokio::io::split(stream_a);
186 let (mut b_read, mut b_write) = tokio::io::split(stream_b);
187
188 let a_to_b = async {
189 let mut buf = [0u8; 8192];
190 let mut ended = false;
191 loop {
192 let read_result = match idle_timeout {
193 Some(timeout) => match tokio::time::timeout(timeout, a_read.read(&mut buf)).await {
194 Ok(result) => result,
195 Err(_) => {
196 ended = true;
197 break;
198 }
199 },
200 None => a_read.read(&mut buf).await,
201 };
202 match read_result {
203 Ok(0) => {
204 ended = true;
205 break;
206 }
207 Ok(n) => {
208 if b_write.write_all(&buf[..n]).await.is_err() {
209 break;
210 }
211 }
212 Err(_) => break,
213 }
214 }
215 let _ = b_write.shutdown().await;
216 ended
217 };
218
219 let b_to_a = async {
220 let mut buf = [0u8; 8192];
221 let mut ended = false;
222 loop {
223 let read_result = match idle_timeout {
224 Some(timeout) => match tokio::time::timeout(timeout, b_read.read(&mut buf)).await {
225 Ok(result) => result,
226 Err(_) => {
227 ended = true;
228 break;
229 }
230 },
231 None => b_read.read(&mut buf).await,
232 };
233 match read_result {
234 Ok(0) => {
235 ended = true;
236 break;
237 }
238 Ok(n) => {
239 if a_write.write_all(&buf[..n]).await.is_err() {
240 break;
241 }
242 }
243 Err(_) => break,
244 }
245 }
246 let _ = a_write.shutdown().await;
247 ended
248 };
249
250 let (a_done, b_done) = tokio::join!(a_to_b, b_to_a);
251 let _ = (a_done, b_done);
252 Ok(())
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[test]
260 fn parse_auth_str_normal() {
261 let (user, pass) = parse_auth_str("user:pass");
262 assert_eq!(user, "user");
263 assert_eq!(pass, "pass");
264 }
265
266 #[test]
267 fn parse_auth_str_empty() {
268 let (user, pass) = parse_auth_str("");
269 assert_eq!(user, "");
270 assert_eq!(pass, "");
271 }
272
273 #[test]
274 fn parse_auth_str_no_colon() {
275 let (user, pass) = parse_auth_str("nocolon");
276 assert_eq!(user, "nocolon");
277 assert_eq!(pass, "");
278 }
279
280 #[test]
281 fn parse_auth_str_multiple_colons() {
282 let (user, pass) = parse_auth_str("user:pass:extra");
283 assert_eq!(user, "user");
284 assert_eq!(pass, "pass:extra");
285 }
286
287 #[test]
288 fn redact_auth_basic() {
289 assert_eq!(redact_auth("user:pass"), "user:****");
290 }
291
292 #[test]
293 fn redact_auth_no_colon() {
294 assert_eq!(redact_auth("opaque"), "****");
295 }
296
297 #[test]
298 fn redact_auth_empty() {
299 assert_eq!(redact_auth(""), "");
300 }
301
302 #[test]
303 fn redact_auth_password_contains_colon() {
304 assert_eq!(redact_auth("user:p:a:s:s"), "user:****");
306 }
307
308 #[test]
309 fn redact_auth_does_not_leak_password() {
310 let s = redact_auth("user:supersecret123");
311 assert!(!s.contains("supersecret"));
312 assert!(!s.contains("secret"));
313 }
314
315 #[test]
316 fn handshake_constants() {
317 assert_eq!(HANDSHAKE_ACCEPT, 0x01);
318 assert_eq!(HANDSHAKE_REJECT, 0x00);
319 }
320
321 #[test]
322 fn control_state_variants() {
323 let state = ControlState::Disconnected;
324 assert_eq!(state, ControlState::Disconnected);
325
326 let state = ControlState::Connecting;
327 assert_eq!(state, ControlState::Connecting);
328
329 let state = ControlState::Authenticating;
330 assert_eq!(state, ControlState::Authenticating);
331
332 let state = ControlState::Ready;
333 assert_eq!(state, ControlState::Ready);
334
335 let state = ControlState::Draining;
336 assert_eq!(state, ControlState::Draining);
337
338 let state = ControlState::Closed;
339 assert_eq!(state, ControlState::Closed);
340 }
341
342 #[tokio::test]
343 async fn auth_handshake_success() {
344 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
345 let addr = listener.local_addr().unwrap();
346
347 let server = tokio::spawn(async move {
348 let (mut stream, _) = listener.accept().await.unwrap();
349 let result = server_auth_handshake(&mut stream, Some("user"), Some("pass")).await;
350 assert!(result.is_ok());
351 assert_eq!(result.unwrap(), "user:****");
353 });
354
355 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
356 let result = client_auth_handshake(&mut stream, "user", "pass").await;
357 assert!(result.is_ok());
358
359 server.await.unwrap();
360 }
361
362 #[tokio::test]
363 async fn auth_handshake_failure() {
364 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
365 let addr = listener.local_addr().unwrap();
366
367 let server = tokio::spawn(async move {
368 let (mut stream, _) = listener.accept().await.unwrap();
369 let result = server_auth_handshake(&mut stream, Some("user"), Some("pass")).await;
370 assert!(result.is_err());
371 });
372
373 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
374 let result = client_auth_handshake(&mut stream, "user", "wrong").await;
375 assert!(result.is_err());
376
377 server.await.unwrap();
378 }
379
380 #[tokio::test]
381 async fn auth_no_credentials_configured() {
382 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
383 let addr = listener.local_addr().unwrap();
384
385 let server = tokio::spawn(async move {
386 let (mut stream, _) = listener.accept().await.unwrap();
387 let result = server_auth_handshake(&mut stream, None, None).await;
388 assert!(result.is_ok());
389 });
390
391 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
392 let result = client_auth_handshake(&mut stream, "", "").await;
393 assert!(result.is_ok());
394
395 server.await.unwrap();
396 }
397
398 #[tokio::test]
399 async fn relay_bidirectional_data() {
400 let listener_a = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
401 let addr_a = listener_a.local_addr().unwrap();
402 let listener_b = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
403 let addr_b = listener_b.local_addr().unwrap();
404
405 let conn_a =
407 tokio::spawn(async move { tokio::net::TcpStream::connect(addr_a).await.unwrap() });
408
409 let conn_b =
411 tokio::spawn(async move { tokio::net::TcpStream::connect(addr_b).await.unwrap() });
412
413 let (stream_a, _) = listener_a.accept().await.unwrap();
414 let (stream_b, _) = listener_b.accept().await.unwrap();
415
416 let relay_handle = tokio::spawn(async move {
418 let _ = relay_bidirectional(stream_a, stream_b).await;
419 });
420
421 let mut conn_a = conn_a.await.unwrap();
422 let mut conn_b = conn_b.await.unwrap();
423
424 tokio::io::AsyncWriteExt::write_all(&mut conn_a, b"hello from A")
426 .await
427 .unwrap();
428
429 let mut buf = [0u8; 1024];
431 let n = tokio::io::AsyncReadExt::read(&mut conn_b, &mut buf)
432 .await
433 .unwrap();
434 assert_eq!(&buf[..n], b"hello from A");
435
436 tokio::io::AsyncWriteExt::write_all(&mut conn_b, b"hello from B")
438 .await
439 .unwrap();
440
441 let n = tokio::io::AsyncReadExt::read(&mut conn_a, &mut buf)
443 .await
444 .unwrap();
445 assert_eq!(&buf[..n], b"hello from B");
446
447 drop(conn_a);
449 drop(conn_b);
450 let _ = relay_handle.await;
451 }
452}