eggress_protocol_reverse/
lib.rs1use std::time::Duration;
2use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
3
4pub mod client;
5pub mod compat_pproxy;
6pub mod metrics;
7pub mod server;
8pub mod tls;
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("tls error: {0}")]
30 Tls(String),
31 #[error("IO error: {0}")]
32 Io(#[from] std::io::Error),
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum ControlState {
38 Disconnected,
39 Connecting,
40 Authenticating,
41 Ready,
42 Draining,
43 Closed,
44}
45
46pub async fn write_auth<S>(
60 stream: &mut S,
61 username: &str,
62 password: &str,
63) -> Result<(), ProtocolError>
64where
65 S: AsyncWrite + Unpin,
66{
67 let auth = format!("{}:{}\n", username, password);
68 stream.write_all(auth.as_bytes()).await?;
69 stream.flush().await?;
70 Ok(())
71}
72
73pub async fn read_handshake<S>(stream: &mut S) -> Result<(), ProtocolError>
75where
76 S: AsyncRead + Unpin,
77{
78 let mut buf = [0u8; 1];
79 stream.read_exact(&mut buf).await?;
80 if buf[0] == HANDSHAKE_REJECT {
81 return Err(ProtocolError::AuthFailed);
82 }
83 Ok(())
84}
85
86pub async fn write_handshake_accept<S>(stream: &mut S) -> Result<(), ProtocolError>
88where
89 S: AsyncWrite + Unpin,
90{
91 stream.write_all(&[HANDSHAKE_ACCEPT]).await?;
92 Ok(())
93}
94
95pub async fn write_handshake_reject<S>(stream: &mut S) -> Result<(), ProtocolError>
97where
98 S: AsyncWrite + Unpin,
99{
100 stream.write_all(&[HANDSHAKE_REJECT]).await?;
101 Ok(())
102}
103
104pub async fn client_auth_handshake<S>(
106 stream: &mut S,
107 username: &str,
108 password: &str,
109) -> Result<(), ProtocolError>
110where
111 S: AsyncRead + AsyncWrite + Unpin,
112{
113 write_auth(stream, username, password).await?;
114 read_handshake(stream).await
115}
116
117pub async fn server_auth_handshake<S>(
123 stream: &mut S,
124 expected_user: Option<&str>,
125 expected_pass: Option<&str>,
126) -> Result<String, ProtocolError>
127where
128 S: AsyncRead + AsyncWrite + Unpin,
129{
130 const MAX_AUTH_BYTES: u64 = 4096;
133 let mut auth_buf = Vec::with_capacity(1024);
134 {
135 let mut limited = (&mut *stream).take(MAX_AUTH_BYTES);
136 let mut reader = tokio::io::BufReader::new(&mut limited);
137 reader.read_until(b'\n', &mut auth_buf).await?;
138 }
139 if auth_buf.is_empty() {
140 return Err(ProtocolError::ConnectionClosed);
141 }
142 if auth_buf.len() > MAX_AUTH_BYTES as usize {
143 return Err(ProtocolError::ConfigInvalid(
144 "auth payload exceeds maximum length".to_string(),
145 ));
146 }
147 if auth_buf.last() != Some(&b'\n') {
150 return Err(ProtocolError::AuthFailed);
151 }
152 auth_buf.pop();
153
154 let auth_str = String::from_utf8_lossy(&auth_buf).to_string();
155
156 match (expected_user, expected_pass) {
159 (Some(exp_user), Some(exp_pass)) => {
160 let (user, pass) = parse_auth_str(&auth_str);
161 use subtle::ConstantTimeEq;
162 let user_ok: bool = user.as_bytes().ct_eq(exp_user.as_bytes()).into();
163 let pass_ok: bool = pass.as_bytes().ct_eq(exp_pass.as_bytes()).into();
164 if !user_ok || !pass_ok {
165 write_handshake_reject(stream).await?;
166 return Err(ProtocolError::AuthFailed);
167 }
168 }
169 (Some(_), None) | (None, Some(_)) => {
170 return Err(ProtocolError::ConfigInvalid(
171 "reverse auth requires both username and password to be configured".to_string(),
172 ));
173 }
174 (None, None) => {}
175 }
176
177 write_handshake_accept(stream).await?;
178 Ok(redact_auth(&auth_str))
179}
180
181pub fn redact_auth(auth: &str) -> String {
186 let (user, _) = parse_auth_str(auth);
187 if user.is_empty() && auth.is_empty() {
188 return String::new();
189 }
190 if !auth.contains(':') {
191 return "****".to_string();
192 }
193 format!("{}:****", user)
194}
195
196fn parse_auth_str(auth: &str) -> (&str, &str) {
198 match auth.find(':') {
199 Some(idx) => (&auth[..idx], &auth[idx + 1..]),
200 None => (auth, ""),
201 }
202}
203
204pub async fn relay_bidirectional(
208 stream_a: tokio::net::TcpStream,
209 stream_b: tokio::net::TcpStream,
210) -> Result<(), ProtocolError> {
211 relay_bidirectional_with_timeout(stream_a, stream_b, None).await
212}
213
214pub async fn relay_bidirectional_with_timeout(
219 stream_a: tokio::net::TcpStream,
220 stream_b: tokio::net::TcpStream,
221 idle_timeout: Option<Duration>,
222) -> Result<(), ProtocolError> {
223 let a: eggress_core::BoxStream = Box::new(stream_a);
224 let b: eggress_core::BoxStream = Box::new(stream_b);
225 relay_bidirectional_boxed(a, b, idle_timeout).await
226}
227
228pub async fn relay_bidirectional_boxed(
234 stream_a: eggress_core::BoxStream,
235 stream_b: eggress_core::BoxStream,
236 idle_timeout: Option<Duration>,
237) -> Result<(), ProtocolError> {
238 let (mut a_read, mut a_write) = tokio::io::split(stream_a);
239 let (mut b_read, mut b_write) = tokio::io::split(stream_b);
240
241 let a_to_b = async {
242 let mut buf = [0u8; 8192];
243 let mut ended = false;
244 loop {
245 let read_result = match idle_timeout {
246 Some(timeout) => match tokio::time::timeout(timeout, a_read.read(&mut buf)).await {
247 Ok(result) => result,
248 Err(_) => {
249 ended = true;
250 break;
251 }
252 },
253 None => a_read.read(&mut buf).await,
254 };
255 match read_result {
256 Ok(0) => {
257 ended = true;
258 break;
259 }
260 Ok(n) => {
261 if b_write.write_all(&buf[..n]).await.is_err() {
262 break;
263 }
264 }
265 Err(_) => break,
266 }
267 }
268 let _ = b_write.shutdown().await;
269 ended
270 };
271
272 let b_to_a = async {
273 let mut buf = [0u8; 8192];
274 let mut ended = false;
275 loop {
276 let read_result = match idle_timeout {
277 Some(timeout) => match tokio::time::timeout(timeout, b_read.read(&mut buf)).await {
278 Ok(result) => result,
279 Err(_) => {
280 ended = true;
281 break;
282 }
283 },
284 None => b_read.read(&mut buf).await,
285 };
286 match read_result {
287 Ok(0) => {
288 ended = true;
289 break;
290 }
291 Ok(n) => {
292 if a_write.write_all(&buf[..n]).await.is_err() {
293 break;
294 }
295 }
296 Err(_) => break,
297 }
298 }
299 let _ = a_write.shutdown().await;
300 ended
301 };
302
303 let (a_done, b_done) = tokio::join!(a_to_b, b_to_a);
304 let _ = (a_done, b_done);
305 Ok(())
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn parse_auth_str_normal() {
314 let (user, pass) = parse_auth_str("user:pass");
315 assert_eq!(user, "user");
316 assert_eq!(pass, "pass");
317 }
318
319 #[test]
320 fn parse_auth_str_empty() {
321 let (user, pass) = parse_auth_str("");
322 assert_eq!(user, "");
323 assert_eq!(pass, "");
324 }
325
326 #[test]
327 fn parse_auth_str_no_colon() {
328 let (user, pass) = parse_auth_str("nocolon");
329 assert_eq!(user, "nocolon");
330 assert_eq!(pass, "");
331 }
332
333 #[test]
334 fn parse_auth_str_multiple_colons() {
335 let (user, pass) = parse_auth_str("user:pass:extra");
336 assert_eq!(user, "user");
337 assert_eq!(pass, "pass:extra");
338 }
339
340 #[test]
341 fn redact_auth_basic() {
342 assert_eq!(redact_auth("user:pass"), "user:****");
343 }
344
345 #[test]
346 fn redact_auth_no_colon() {
347 assert_eq!(redact_auth("opaque"), "****");
348 }
349
350 #[test]
351 fn redact_auth_empty() {
352 assert_eq!(redact_auth(""), "");
353 }
354
355 #[test]
356 fn redact_auth_password_contains_colon() {
357 assert_eq!(redact_auth("user:p:a:s:s"), "user:****");
359 }
360
361 #[test]
362 fn redact_auth_does_not_leak_password() {
363 let s = redact_auth("user:supersecret123");
364 assert!(!s.contains("supersecret"));
365 assert!(!s.contains("secret"));
366 }
367
368 #[test]
369 fn handshake_constants() {
370 assert_eq!(HANDSHAKE_ACCEPT, 0x01);
371 assert_eq!(HANDSHAKE_REJECT, 0x00);
372 }
373
374 #[test]
375 fn control_state_variants() {
376 let state = ControlState::Disconnected;
377 assert_eq!(state, ControlState::Disconnected);
378
379 let state = ControlState::Connecting;
380 assert_eq!(state, ControlState::Connecting);
381
382 let state = ControlState::Authenticating;
383 assert_eq!(state, ControlState::Authenticating);
384
385 let state = ControlState::Ready;
386 assert_eq!(state, ControlState::Ready);
387
388 let state = ControlState::Draining;
389 assert_eq!(state, ControlState::Draining);
390
391 let state = ControlState::Closed;
392 assert_eq!(state, ControlState::Closed);
393 }
394
395 #[tokio::test]
396 async fn auth_handshake_success() {
397 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
398 let addr = listener.local_addr().unwrap();
399
400 let server = tokio::spawn(async move {
401 let (mut stream, _) = listener.accept().await.unwrap();
402 let result = server_auth_handshake(&mut stream, Some("user"), Some("pass")).await;
403 assert!(result.is_ok());
404 assert_eq!(result.unwrap(), "user:****");
406 });
407
408 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
409 let result = client_auth_handshake(&mut stream, "user", "pass").await;
410 assert!(result.is_ok());
411
412 server.await.unwrap();
413 }
414
415 #[tokio::test]
416 async fn auth_handshake_failure() {
417 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
418 let addr = listener.local_addr().unwrap();
419
420 let server = tokio::spawn(async move {
421 let (mut stream, _) = listener.accept().await.unwrap();
422 let result = server_auth_handshake(&mut stream, Some("user"), Some("pass")).await;
423 assert!(result.is_err());
424 });
425
426 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
427 let result = client_auth_handshake(&mut stream, "user", "wrong").await;
428 assert!(result.is_err());
429
430 server.await.unwrap();
431 }
432
433 #[tokio::test]
434 async fn auth_no_credentials_configured() {
435 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
436 let addr = listener.local_addr().unwrap();
437
438 let server = tokio::spawn(async move {
439 let (mut stream, _) = listener.accept().await.unwrap();
440 let result = server_auth_handshake(&mut stream, None, None).await;
441 assert!(result.is_ok());
442 });
443
444 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
445 let result = client_auth_handshake(&mut stream, "", "").await;
446 assert!(result.is_ok());
447
448 server.await.unwrap();
449 }
450
451 #[tokio::test]
452 async fn relay_bidirectional_data() {
453 let listener_a = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
454 let addr_a = listener_a.local_addr().unwrap();
455 let listener_b = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
456 let addr_b = listener_b.local_addr().unwrap();
457
458 let conn_a =
460 tokio::spawn(async move { tokio::net::TcpStream::connect(addr_a).await.unwrap() });
461
462 let conn_b =
464 tokio::spawn(async move { tokio::net::TcpStream::connect(addr_b).await.unwrap() });
465
466 let (stream_a, _) = listener_a.accept().await.unwrap();
467 let (stream_b, _) = listener_b.accept().await.unwrap();
468
469 let relay_handle = tokio::spawn(async move {
471 let _ = relay_bidirectional(stream_a, stream_b).await;
472 });
473
474 let mut conn_a = conn_a.await.unwrap();
475 let mut conn_b = conn_b.await.unwrap();
476
477 tokio::io::AsyncWriteExt::write_all(&mut conn_a, b"hello from A")
479 .await
480 .unwrap();
481
482 let mut buf = [0u8; 1024];
484 let n = tokio::io::AsyncReadExt::read(&mut conn_b, &mut buf)
485 .await
486 .unwrap();
487 assert_eq!(&buf[..n], b"hello from A");
488
489 tokio::io::AsyncWriteExt::write_all(&mut conn_b, b"hello from B")
491 .await
492 .unwrap();
493
494 let n = tokio::io::AsyncReadExt::read(&mut conn_a, &mut buf)
496 .await
497 .unwrap();
498 assert_eq!(&buf[..n], b"hello from B");
499
500 drop(conn_a);
502 drop(conn_b);
503 let _ = relay_handle.await;
504 }
505}