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(
55 stream: &mut TcpStream,
56 username: &str,
57 password: &str,
58) -> Result<(), ProtocolError> {
59 let auth = format!("{}:{}\n", username, password);
60 stream.write_all(auth.as_bytes()).await?;
61 stream.flush().await?;
62 Ok(())
63}
64
65pub async fn read_handshake(stream: &mut TcpStream) -> Result<(), ProtocolError> {
67 let mut buf = [0u8; 1];
68 stream.read_exact(&mut buf).await?;
69 if buf[0] == HANDSHAKE_REJECT {
70 return Err(ProtocolError::AuthFailed);
71 }
72 Ok(())
73}
74
75pub async fn write_handshake_accept(stream: &mut TcpStream) -> Result<(), ProtocolError> {
77 stream.write_all(&[HANDSHAKE_ACCEPT]).await?;
78 Ok(())
79}
80
81pub async fn write_handshake_reject(stream: &mut TcpStream) -> Result<(), ProtocolError> {
83 stream.write_all(&[HANDSHAKE_REJECT]).await?;
84 Ok(())
85}
86
87pub async fn client_auth_handshake(
89 stream: &mut TcpStream,
90 username: &str,
91 password: &str,
92) -> Result<(), ProtocolError> {
93 write_auth(stream, username, password).await?;
94 read_handshake(stream).await
95}
96
97pub async fn server_auth_handshake(
103 stream: &mut TcpStream,
104 expected_user: Option<&str>,
105 expected_pass: Option<&str>,
106) -> Result<String, ProtocolError> {
107 const MAX_AUTH_BYTES: u64 = 4096;
110 let mut auth_buf = Vec::with_capacity(1024);
111 {
112 use tokio::io::AsyncReadExt;
113 let mut limited = (&mut *stream).take(MAX_AUTH_BYTES);
114 let mut reader = tokio::io::BufReader::new(&mut limited);
115 reader.read_until(b'\n', &mut auth_buf).await?;
116 }
117 if auth_buf.is_empty() {
118 return Err(ProtocolError::ConnectionClosed);
119 }
120 if auth_buf.len() > MAX_AUTH_BYTES as usize {
121 return Err(ProtocolError::ConfigInvalid(
122 "auth payload exceeds maximum length".to_string(),
123 ));
124 }
125 if auth_buf.last() != Some(&b'\n') {
128 return Err(ProtocolError::AuthFailed);
129 }
130 auth_buf.pop();
131
132 let auth_str = String::from_utf8_lossy(&auth_buf).to_string();
133
134 match (expected_user, expected_pass) {
137 (Some(exp_user), Some(exp_pass)) => {
138 let (user, pass) = parse_auth_str(&auth_str);
139 use subtle::ConstantTimeEq;
140 let user_ok: bool = user.as_bytes().ct_eq(exp_user.as_bytes()).into();
141 let pass_ok: bool = pass.as_bytes().ct_eq(exp_pass.as_bytes()).into();
142 if !user_ok || !pass_ok {
143 write_handshake_reject(stream).await?;
144 return Err(ProtocolError::AuthFailed);
145 }
146 }
147 (Some(_), None) | (None, Some(_)) => {
148 return Err(ProtocolError::ConfigInvalid(
149 "reverse auth requires both username and password to be configured".to_string(),
150 ));
151 }
152 (None, None) => {}
153 }
154
155 write_handshake_accept(stream).await?;
156 Ok(redact_auth(&auth_str))
157}
158
159pub fn redact_auth(auth: &str) -> String {
164 let (user, _) = parse_auth_str(auth);
165 if user.is_empty() && auth.is_empty() {
166 return String::new();
167 }
168 if !auth.contains(':') {
169 return "****".to_string();
170 }
171 format!("{}:****", user)
172}
173
174fn parse_auth_str(auth: &str) -> (&str, &str) {
176 match auth.find(':') {
177 Some(idx) => (&auth[..idx], &auth[idx + 1..]),
178 None => (auth, ""),
179 }
180}
181
182pub async fn relay_bidirectional(
186 stream_a: TcpStream,
187 stream_b: TcpStream,
188) -> Result<(), ProtocolError> {
189 relay_bidirectional_with_timeout(stream_a, stream_b, None).await
190}
191
192pub async fn relay_bidirectional_with_timeout(
197 stream_a: TcpStream,
198 stream_b: TcpStream,
199 idle_timeout: Option<Duration>,
200) -> Result<(), ProtocolError> {
201 let (mut a_read, mut a_write) = tokio::io::split(stream_a);
202 let (mut b_read, mut b_write) = tokio::io::split(stream_b);
203
204 let a_to_b = async {
205 let mut buf = [0u8; 8192];
206 let mut ended = false;
207 loop {
208 let read_result = match idle_timeout {
209 Some(timeout) => match tokio::time::timeout(timeout, a_read.read(&mut buf)).await {
210 Ok(result) => result,
211 Err(_) => {
212 ended = true;
213 break;
214 }
215 },
216 None => a_read.read(&mut buf).await,
217 };
218 match read_result {
219 Ok(0) => {
220 ended = true;
221 break;
222 }
223 Ok(n) => {
224 if b_write.write_all(&buf[..n]).await.is_err() {
225 break;
226 }
227 }
228 Err(_) => break,
229 }
230 }
231 let _ = b_write.shutdown().await;
232 ended
233 };
234
235 let b_to_a = async {
236 let mut buf = [0u8; 8192];
237 let mut ended = false;
238 loop {
239 let read_result = match idle_timeout {
240 Some(timeout) => match tokio::time::timeout(timeout, b_read.read(&mut buf)).await {
241 Ok(result) => result,
242 Err(_) => {
243 ended = true;
244 break;
245 }
246 },
247 None => b_read.read(&mut buf).await,
248 };
249 match read_result {
250 Ok(0) => {
251 ended = true;
252 break;
253 }
254 Ok(n) => {
255 if a_write.write_all(&buf[..n]).await.is_err() {
256 break;
257 }
258 }
259 Err(_) => break,
260 }
261 }
262 let _ = a_write.shutdown().await;
263 ended
264 };
265
266 let (a_done, b_done) = tokio::join!(a_to_b, b_to_a);
267 let _ = (a_done, b_done);
268 Ok(())
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn parse_auth_str_normal() {
277 let (user, pass) = parse_auth_str("user:pass");
278 assert_eq!(user, "user");
279 assert_eq!(pass, "pass");
280 }
281
282 #[test]
283 fn parse_auth_str_empty() {
284 let (user, pass) = parse_auth_str("");
285 assert_eq!(user, "");
286 assert_eq!(pass, "");
287 }
288
289 #[test]
290 fn parse_auth_str_no_colon() {
291 let (user, pass) = parse_auth_str("nocolon");
292 assert_eq!(user, "nocolon");
293 assert_eq!(pass, "");
294 }
295
296 #[test]
297 fn parse_auth_str_multiple_colons() {
298 let (user, pass) = parse_auth_str("user:pass:extra");
299 assert_eq!(user, "user");
300 assert_eq!(pass, "pass:extra");
301 }
302
303 #[test]
304 fn redact_auth_basic() {
305 assert_eq!(redact_auth("user:pass"), "user:****");
306 }
307
308 #[test]
309 fn redact_auth_no_colon() {
310 assert_eq!(redact_auth("opaque"), "****");
311 }
312
313 #[test]
314 fn redact_auth_empty() {
315 assert_eq!(redact_auth(""), "");
316 }
317
318 #[test]
319 fn redact_auth_password_contains_colon() {
320 assert_eq!(redact_auth("user:p:a:s:s"), "user:****");
322 }
323
324 #[test]
325 fn redact_auth_does_not_leak_password() {
326 let s = redact_auth("user:supersecret123");
327 assert!(!s.contains("supersecret"));
328 assert!(!s.contains("secret"));
329 }
330
331 #[test]
332 fn handshake_constants() {
333 assert_eq!(HANDSHAKE_ACCEPT, 0x01);
334 assert_eq!(HANDSHAKE_REJECT, 0x00);
335 }
336
337 #[test]
338 fn control_state_variants() {
339 let state = ControlState::Disconnected;
340 assert_eq!(state, ControlState::Disconnected);
341
342 let state = ControlState::Connecting;
343 assert_eq!(state, ControlState::Connecting);
344
345 let state = ControlState::Authenticating;
346 assert_eq!(state, ControlState::Authenticating);
347
348 let state = ControlState::Ready;
349 assert_eq!(state, ControlState::Ready);
350
351 let state = ControlState::Draining;
352 assert_eq!(state, ControlState::Draining);
353
354 let state = ControlState::Closed;
355 assert_eq!(state, ControlState::Closed);
356 }
357
358 #[tokio::test]
359 async fn auth_handshake_success() {
360 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
361 let addr = listener.local_addr().unwrap();
362
363 let server = tokio::spawn(async move {
364 let (mut stream, _) = listener.accept().await.unwrap();
365 let result = server_auth_handshake(&mut stream, Some("user"), Some("pass")).await;
366 assert!(result.is_ok());
367 assert_eq!(result.unwrap(), "user:****");
369 });
370
371 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
372 let result = client_auth_handshake(&mut stream, "user", "pass").await;
373 assert!(result.is_ok());
374
375 server.await.unwrap();
376 }
377
378 #[tokio::test]
379 async fn auth_handshake_failure() {
380 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
381 let addr = listener.local_addr().unwrap();
382
383 let server = tokio::spawn(async move {
384 let (mut stream, _) = listener.accept().await.unwrap();
385 let result = server_auth_handshake(&mut stream, Some("user"), Some("pass")).await;
386 assert!(result.is_err());
387 });
388
389 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
390 let result = client_auth_handshake(&mut stream, "user", "wrong").await;
391 assert!(result.is_err());
392
393 server.await.unwrap();
394 }
395
396 #[tokio::test]
397 async fn auth_no_credentials_configured() {
398 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
399 let addr = listener.local_addr().unwrap();
400
401 let server = tokio::spawn(async move {
402 let (mut stream, _) = listener.accept().await.unwrap();
403 let result = server_auth_handshake(&mut stream, None, None).await;
404 assert!(result.is_ok());
405 });
406
407 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
408 let result = client_auth_handshake(&mut stream, "", "").await;
409 assert!(result.is_ok());
410
411 server.await.unwrap();
412 }
413
414 #[tokio::test]
415 async fn relay_bidirectional_data() {
416 let listener_a = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
417 let addr_a = listener_a.local_addr().unwrap();
418 let listener_b = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
419 let addr_b = listener_b.local_addr().unwrap();
420
421 let conn_a =
423 tokio::spawn(async move { tokio::net::TcpStream::connect(addr_a).await.unwrap() });
424
425 let conn_b =
427 tokio::spawn(async move { tokio::net::TcpStream::connect(addr_b).await.unwrap() });
428
429 let (stream_a, _) = listener_a.accept().await.unwrap();
430 let (stream_b, _) = listener_b.accept().await.unwrap();
431
432 let relay_handle = tokio::spawn(async move {
434 let _ = relay_bidirectional(stream_a, stream_b).await;
435 });
436
437 let mut conn_a = conn_a.await.unwrap();
438 let mut conn_b = conn_b.await.unwrap();
439
440 tokio::io::AsyncWriteExt::write_all(&mut conn_a, b"hello from A")
442 .await
443 .unwrap();
444
445 let mut buf = [0u8; 1024];
447 let n = tokio::io::AsyncReadExt::read(&mut conn_b, &mut buf)
448 .await
449 .unwrap();
450 assert_eq!(&buf[..n], b"hello from A");
451
452 tokio::io::AsyncWriteExt::write_all(&mut conn_b, b"hello from B")
454 .await
455 .unwrap();
456
457 let n = tokio::io::AsyncReadExt::read(&mut conn_a, &mut buf)
459 .await
460 .unwrap();
461 assert_eq!(&buf[..n], b"hello from B");
462
463 drop(conn_a);
465 drop(conn_b);
466 let _ = relay_handle.await;
467 }
468}