1use crate::client::TargetResolver;
10use crate::{relay_bidirectional_with_timeout, ProtocolError};
11use eggress_core::{TargetAddr, TargetHost};
12use eggress_uri::{ProtocolSpec, ProxyChainSpec};
13use std::net::SocketAddr;
14use std::sync::atomic::{AtomicUsize, Ordering};
15use std::sync::Arc;
16use std::time::Duration;
17use tokio::io::{AsyncReadExt, AsyncWriteExt};
18use tokio::net::{TcpListener, TcpStream};
19use tokio::sync::mpsc;
20use tokio::task::JoinSet;
21use tokio_util::sync::CancellationToken;
22use tracing::{debug, info, warn};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum PproxyBackwardState {
28 Disconnected,
29 Connecting,
30 Authenticating,
31 ReadyChannel,
32 Retrying,
33 Closed,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
43pub enum PproxyBackwardFraming {
44 #[default]
48 Raw,
49 Socks5,
53}
54
55#[derive(Debug, Clone)]
57pub struct PproxyBackwardClientConfig {
58 pub server_addr: SocketAddr,
59 pub server_chain: Option<ProxyChainSpec>,
62 pub auth: Vec<u8>,
64 pub reconnect_initial_ms: u64,
65 pub reconnect_max_ms: u64,
66 pub read_timeout_ms: u64,
67 pub target_connect_timeout_ms: u64,
68 pub server_framing: PproxyBackwardFraming,
70}
71
72impl Default for PproxyBackwardClientConfig {
73 fn default() -> Self {
74 Self {
75 server_addr: "127.0.0.1:0".parse().expect("valid default socket address"),
76 server_chain: None,
77 auth: Vec::new(),
78 reconnect_initial_ms: 100,
79 reconnect_max_ms: 30_000,
80 read_timeout_ms: 60_000,
81 target_connect_timeout_ms: 10_000,
82 server_framing: PproxyBackwardFraming::default(),
83 }
84 }
85}
86
87pub struct PproxyBackwardClient {
90 config: PproxyBackwardClientConfig,
91 cancel: CancellationToken,
92 resolver: Arc<dyn TargetResolver>,
93}
94
95impl PproxyBackwardClient {
96 pub fn new(config: PproxyBackwardClientConfig, resolver: Arc<dyn TargetResolver>) -> Self {
97 Self {
98 config,
99 cancel: CancellationToken::new(),
100 resolver,
101 }
102 }
103
104 pub fn cancel_token(&self) -> CancellationToken {
105 self.cancel.clone()
106 }
107
108 pub async fn run(&self) -> Result<(), ProtocolError> {
109 let mut backoff = self.config.reconnect_initial_ms.max(1);
110 loop {
111 if self.cancel.is_cancelled() {
112 break;
113 }
114
115 match self.run_connection().await {
116 Ok(()) => backoff = self.config.reconnect_initial_ms.max(1),
117 Err(error) => {
118 if self.cancel.is_cancelled() {
119 break;
120 }
121 warn!(error = %error, backoff_ms = backoff, "pproxy backward channel failed");
122 let delay = tokio::time::sleep(Duration::from_millis(backoff));
123 tokio::pin!(delay);
124 tokio::select! {
125 _ = &mut delay => {}
126 _ = self.cancel.cancelled() => break,
127 }
128 backoff = backoff
129 .saturating_mul(2)
130 .min(self.config.reconnect_max_ms.max(1));
131 }
132 }
133 }
134 Ok(())
135 }
136
137 async fn run_connection(&self) -> Result<(), ProtocolError> {
138 let mut stream = self.connect_control().await?;
139 if !self.config.auth.is_empty() {
140 stream.write_all(&self.config.auth).await?;
143 stream.flush().await?;
144 }
145
146 match self.config.server_framing {
147 PproxyBackwardFraming::Raw => self.run_connection_raw(stream).await,
148 PproxyBackwardFraming::Socks5 => self.run_connection_socks5(stream).await,
149 }
150 }
151
152 async fn run_connection_raw(&self, stream: TcpStream) -> Result<(), ProtocolError> {
157 let (host, port) = match self.resolver.resolve() {
158 crate::client::TargetResolution::Connect { host, port } => (host, port),
159 crate::client::TargetResolution::Reject { reason } => {
160 return Err(ProtocolError::ConfigInvalid(format!(
161 "pproxy backward route rejected: {reason}"
162 )))
163 }
164 };
165
166 let timeout = Duration::from_millis(self.config.target_connect_timeout_ms.max(1));
167 let target = tokio::time::timeout(timeout, TcpStream::connect((host.as_str(), port)))
168 .await
169 .map_err(|_| {
170 ProtocolError::Io(std::io::Error::new(
171 std::io::ErrorKind::TimedOut,
172 "pproxy backward target connect timed out",
173 ))
174 })??;
175
176 relay_bidirectional_with_timeout(
177 stream,
178 target,
179 (self.config.read_timeout_ms > 0)
180 .then(|| Duration::from_millis(self.config.read_timeout_ms)),
181 )
182 .await
183 }
184
185 async fn run_connection_socks5(&self, mut stream: TcpStream) -> Result<(), ProtocolError> {
191 const SOCKS5_VERSION: u8 = 0x05;
192 const SOCKS5_METHOD_NONE: u8 = 0x00;
193 const SOCKS5_CMD_CONNECT: u8 = 0x01;
194 const SOCKS5_RSV: u8 = 0x00;
195 const SOCKS5_ATYP_IPV4: u8 = 0x01;
196 const SOCKS5_ATYP_DOMAIN: u8 = 0x03;
197 const SOCKS5_ATYP_IPV6: u8 = 0x04;
198 const SOCKS5_REP_SUCCESS: u8 = 0x00;
199
200 let mut header = [0u8; 2];
202 stream.read_exact(&mut header).await?;
203 if header[0] != SOCKS5_VERSION {
204 return Err(ProtocolError::ConfigInvalid(format!(
205 "pproxy backward SOCKS5 hello version mismatch: {}",
206 header[0]
207 )));
208 }
209 let nmethods = header[1] as usize;
210 let mut methods = vec![0u8; nmethods];
211 if nmethods > 0 {
212 stream.read_exact(&mut methods).await?;
213 }
214 if !methods.contains(&SOCKS5_METHOD_NONE) {
215 stream.write_all(&[SOCKS5_VERSION, 0xff]).await?;
216 stream.flush().await?;
217 return Err(ProtocolError::AuthFailed);
218 }
219 stream
220 .write_all(&[SOCKS5_VERSION, SOCKS5_METHOD_NONE])
221 .await?;
222 stream.flush().await?;
223
224 let mut req_header = [0u8; 4];
226 stream.read_exact(&mut req_header).await?;
227 if req_header[0] != SOCKS5_VERSION || req_header[1] != SOCKS5_CMD_CONNECT {
228 return Err(ProtocolError::ConfigInvalid(format!(
229 "pproxy backward SOCKS5 request header invalid: {:?}",
230 &req_header[..]
231 )));
232 }
233 let _rsv = req_header[2];
234 if req_header[2] != SOCKS5_RSV {
235 return Err(ProtocolError::ConfigInvalid(format!(
236 "pproxy backward SOCKS5 RSV must be zero, got {}",
237 req_header[2]
238 )));
239 }
240 let atyp = req_header[3];
241 let host = match atyp {
242 SOCKS5_ATYP_IPV4 => {
243 let mut addr = [0u8; 4];
244 stream.read_exact(&mut addr).await?;
245 std::net::IpAddr::V4(std::net::Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3]))
246 .to_string()
247 }
248 SOCKS5_ATYP_DOMAIN => {
249 let mut len = [0u8; 1];
250 stream.read_exact(&mut len).await?;
251 let n = len[0] as usize;
252 if n == 0 {
253 return Err(ProtocolError::ConfigInvalid(
254 "pproxy backward SOCKS5 domain length zero".into(),
255 ));
256 }
257 let mut domain = vec![0u8; n];
258 stream.read_exact(&mut domain).await?;
259 String::from_utf8(domain)
260 .map_err(|_| ProtocolError::ConfigInvalid("invalid SOCKS5 domain".into()))?
261 }
262 SOCKS5_ATYP_IPV6 => {
263 let mut addr = [0u8; 16];
264 stream.read_exact(&mut addr).await?;
265 std::net::IpAddr::V6(std::net::Ipv6Addr::from(addr)).to_string()
266 }
267 _ => {
268 stream
269 .write_all(&[
270 SOCKS5_VERSION,
271 0x08,
272 SOCKS5_RSV,
273 SOCKS5_ATYP_IPV4,
274 0,
275 0,
276 0,
277 0,
278 0,
279 0,
280 ])
281 .await?;
282 stream.flush().await?;
283 return Err(ProtocolError::ConfigInvalid(format!(
284 "pproxy backward SOCKS5 ATYP {atyp} unsupported"
285 )));
286 }
287 };
288 let mut port_bytes = [0u8; 2];
289 stream.read_exact(&mut port_bytes).await?;
290 let port = u16::from_be_bytes(port_bytes);
291
292 let timeout = Duration::from_millis(self.config.target_connect_timeout_ms.max(1));
293 let target = tokio::time::timeout(timeout, TcpStream::connect((host.as_str(), port)))
294 .await
295 .map_err(|_| {
296 ProtocolError::Io(std::io::Error::new(
297 std::io::ErrorKind::TimedOut,
298 "pproxy backward SOCKS5 target connect timed out",
299 ))
300 })?;
301 let target = match target {
302 Ok(t) => t,
303 Err(error) => {
304 let _ = stream
307 .write_all(&[
308 SOCKS5_VERSION,
309 0x05,
310 SOCKS5_RSV,
311 SOCKS5_ATYP_IPV4,
312 0,
313 0,
314 0,
315 0,
316 0,
317 0,
318 ])
319 .await;
320 let _ = stream.flush().await;
321 return Err(ProtocolError::Io(error));
322 }
323 };
324
325 stream
326 .write_all(&[
327 SOCKS5_VERSION,
328 SOCKS5_REP_SUCCESS,
329 SOCKS5_RSV,
330 SOCKS5_ATYP_IPV4,
331 0,
332 0,
333 0,
334 0,
335 0,
336 0,
337 ])
338 .await?;
339 stream.flush().await?;
340
341 relay_bidirectional_with_timeout(
342 stream,
343 target,
344 (self.config.read_timeout_ms > 0)
345 .then(|| Duration::from_millis(self.config.read_timeout_ms)),
346 )
347 .await
348 }
349
350 async fn connect_control(&self) -> Result<TcpStream, ProtocolError> {
351 let Some(chain) = self.config.server_chain.as_ref() else {
352 return Ok(TcpStream::connect(self.config.server_addr).await?);
353 };
354 if chain.hops.len() <= 1 {
355 return Ok(TcpStream::connect(self.config.server_addr).await?);
356 }
357 if chain.hops.iter().any(|hop| hop.tls) {
358 return Err(ProtocolError::ConfigInvalid(
359 "TLS-wrapped pproxy backward jumps require a configured TLS transport".into(),
360 ));
361 }
362
363 let last = chain.hops.last().expect("chain length checked");
364 let mut stream =
365 TcpStream::connect((last.endpoint.host.as_str(), last.endpoint.port)).await?;
366 for index in (1..chain.hops.len()).rev() {
367 let jump = &chain.hops[index];
368 let endpoint = &chain.hops[index - 1].endpoint;
369 let target = TargetAddr {
370 host: endpoint
371 .host
372 .parse()
373 .map(TargetHost::Ip)
374 .unwrap_or_else(|_| TargetHost::Domain(endpoint.host.clone())),
375 port: endpoint.port,
376 };
377 stream = connect_jump(stream, jump, &target, self.config.read_timeout_ms).await?;
378 }
379 Ok(stream)
380 }
381
382 pub fn shutdown(&self) {
383 self.cancel.cancel();
384 }
385}
386
387#[derive(Debug, Clone)]
389pub struct PproxyBackwardServerConfig {
390 pub control_bind: SocketAddr,
391 pub external_bind: SocketAddr,
392 pub auth: Vec<u8>,
394 pub max_control_connections: usize,
395 pub max_pending_external: usize,
396 pub read_timeout_ms: u64,
397 pub socks5_target: Option<(String, u16)>,
401 pub client_framing: PproxyBackwardFraming,
407}
408
409impl Default for PproxyBackwardServerConfig {
410 fn default() -> Self {
411 Self {
412 control_bind: "127.0.0.1:0".parse().expect("valid default socket address"),
413 external_bind: "127.0.0.1:0".parse().expect("valid default socket address"),
414 auth: Vec::new(),
415 max_control_connections: 256,
416 max_pending_external: 1024,
417 read_timeout_ms: 300_000,
418 socks5_target: None,
419 client_framing: PproxyBackwardFraming::default(),
420 }
421 }
422}
423
424struct QueuedChannel {
425 stream: TcpStream,
426}
427
428pub struct PproxyBackwardServer {
431 config: PproxyBackwardServerConfig,
432 cancel: CancellationToken,
433}
434
435impl PproxyBackwardServer {
436 pub fn new(config: PproxyBackwardServerConfig) -> Self {
437 Self {
438 config,
439 cancel: CancellationToken::new(),
440 }
441 }
442
443 pub fn cancel_token(&self) -> CancellationToken {
444 self.cancel.clone()
445 }
446
447 pub async fn run(self) -> Result<(), ProtocolError> {
448 let control_listener = TcpListener::bind(self.config.control_bind).await?;
449 let external_listener = TcpListener::bind(self.config.external_bind).await?;
450 let (control_tx, mut control_rx) =
451 mpsc::channel::<QueuedChannel>(self.config.max_control_connections.max(1));
452 let cancel = self.cancel.clone();
453 let config = Arc::new(self.config);
454 let mut tasks = JoinSet::new();
455
456 let accept_cancel = cancel.clone();
457 let accept_config = config.clone();
458 let active_control = Arc::new(AtomicUsize::new(0));
459 tasks.spawn(async move {
460 loop {
461 tokio::select! {
462 result = control_listener.accept() => {
463 let (stream, peer) = match result {
464 Ok(value) => value,
465 Err(error) => {
466 warn!(%error, "pproxy backward control accept failed");
467 continue;
468 }
469 };
470 let max_control_connections =
475 accept_config.max_control_connections.max(1);
476 let prev = active_control.fetch_add(1, Ordering::AcqRel);
477 if prev >= max_control_connections {
478 active_control.fetch_sub(1, Ordering::Relaxed);
479 debug!(
480 %peer,
481 max = max_control_connections,
482 "pproxy backward control connection rejected: max reached"
483 );
484 drop(stream);
485 continue;
486 }
487 let auth = accept_config.auth.clone();
488 let tx = control_tx.clone();
489 let timeout = accept_config.read_timeout_ms;
490 let framing = accept_config.client_framing;
491 let socks5_target = accept_config.socks5_target.clone();
492 let active_control = active_control.clone();
493 tokio::spawn(async move {
494 handle_pproxy_control_channel(
495 stream,
496 peer,
497 auth,
498 timeout,
499 framing,
500 socks5_target,
501 tx,
502 )
503 .await;
504 active_control.fetch_sub(1, Ordering::Relaxed);
505 });
506 }
507 _ = accept_cancel.cancelled() => break,
508 }
509 }
510 });
511
512 let external_cancel = cancel.clone();
513 let external_framing = config.client_framing;
514 let external_target = config.socks5_target.clone();
515 tasks.spawn(async move {
516 let mut relays = JoinSet::new();
517 loop {
518 tokio::select! {
519 result = external_listener.accept() => {
520 let (external, peer) = match result {
521 Ok(value) => value,
522 Err(error) => {
523 warn!(%error, "pproxy backward external accept failed");
524 continue;
525 }
526 };
527 let control = tokio::select! {
528 value = control_rx.recv() => value,
529 _ = external_cancel.cancelled() => break,
530 };
531 let Some(control) = control else { break };
532 let timeout = config.read_timeout_ms;
533 let target = external_target.clone();
534 relays.spawn(async move {
535 debug!(%peer, "relaying pproxy backward channel");
536 let result = relay_pproxy_pair(
537 external,
538 control.stream,
539 target,
540 external_framing,
541 timeout,
542 )
543 .await;
544 if let Err(error) = result {
545 debug!(%peer, %error, "pproxy backward relay finished with error");
546 }
547 });
548 }
549 _ = external_cancel.cancelled() => break,
550 }
551 }
552 relays.abort_all();
553 while relays.join_next().await.is_some() {}
554 });
555
556 cancel.cancelled().await;
557 tasks.abort_all();
558 while tasks.join_next().await.is_some() {}
559 info!("pproxy backward server shut down");
560 Ok(())
561 }
562
563 pub fn shutdown(&self) {
564 self.cancel.cancel();
565 }
566}
567
568async fn handle_pproxy_control_channel(
571 mut stream: TcpStream,
572 peer: SocketAddr,
573 auth: Vec<u8>,
574 timeout_ms: u64,
575 framing: PproxyBackwardFraming,
576 socks5_target: Option<(String, u16)>,
577 tx: mpsc::Sender<QueuedChannel>,
578) {
579 if !auth.is_empty() {
580 let mut received = vec![0u8; auth.len()];
581 let read = tokio::time::timeout(
582 Duration::from_millis(timeout_ms.max(1)),
583 stream.read_exact(&mut received),
584 )
585 .await;
586 use subtle::ConstantTimeEq;
588 let auth_ok =
589 matches!(read, Ok(Ok(_))) && bool::from(received.as_slice().ct_eq(auth.as_slice()));
590 if !auth_ok {
591 debug!(%peer, "pproxy backward auth rejected");
592 return;
593 }
594 }
595 if matches!(framing, PproxyBackwardFraming::Socks5) {
596 if let Err(error) = proxy_socks5_setup(&mut stream).await {
597 debug!(%peer, %error, "pproxy backward SOCKS5 setup failed");
598 return;
599 }
600 if let Some((host, port)) = socks5_target {
601 if let Err(error) = reply_socks5_connect(&mut stream, &host, port).await {
602 debug!(
603 %peer,
604 %error,
605 "pproxy backward SOCKS5 CONNECT reply failed"
606 );
607 return;
608 }
609 if let Err(error) = read_socks5_connect_reply(&mut stream).await {
613 debug!(
614 %peer,
615 %error,
616 "pproxy backward SOCKS5 CONNECT reply read failed"
617 );
618 return;
619 }
620 }
621 }
622 let _ = tx.send(QueuedChannel { stream }).await;
623}
624
625pub fn raw_auth(username: Option<&str>, password: Option<&str>) -> Vec<u8> {
627 match (username, password) {
628 (Some(user), Some(pass)) => format!("{user}:{pass}").into_bytes(),
629 (Some(user), None) => user.as_bytes().to_vec(),
630 (None, Some(pass)) => pass.as_bytes().to_vec(),
631 (None, None) => Vec::new(),
632 }
633}
634
635async fn proxy_socks5_setup(stream: &mut TcpStream) -> Result<(), ProtocolError> {
641 stream.write_all(&[0x05, 0x01, 0x00]).await?;
642 stream.flush().await?;
643 let mut header = [0u8; 2];
644 stream.read_exact(&mut header).await?;
645 if header[0] != 0x05 {
646 return Err(ProtocolError::ConfigInvalid(format!(
647 "SOCKS5 methods selection version mismatch: {}",
648 header[0]
649 )));
650 }
651 if header[1] != 0x00 {
652 return Err(ProtocolError::AuthFailed);
653 }
654 Ok(())
655}
656
657async fn read_socks5_connect_reply(stream: &mut TcpStream) -> Result<(), ProtocolError> {
661 let mut header = [0u8; 4];
662 stream.read_exact(&mut header).await?;
663 if header[0] != 0x05 {
664 return Err(ProtocolError::ConfigInvalid(format!(
665 "SOCKS5 CONNECT reply version mismatch: {}",
666 header[0]
667 )));
668 }
669 if header[1] != 0x00 {
670 return Err(ProtocolError::ConfigInvalid(format!(
671 "SOCKS5 CONNECT reply rep non-success: {}",
672 header[1]
673 )));
674 }
675 match header[3] {
676 0x01 => {
677 let mut tail = [0u8; 6];
678 stream.read_exact(&mut tail).await?;
679 }
680 0x04 => {
681 let mut tail = [0u8; 18];
682 stream.read_exact(&mut tail).await?;
683 }
684 0x03 => {
685 let mut len = [0u8; 1];
686 stream.read_exact(&mut len).await?;
687 let mut tail = vec![0u8; len[0] as usize + 2];
688 stream.read_exact(&mut tail).await?;
689 }
690 other => {
691 return Err(ProtocolError::ConfigInvalid(format!(
692 "SOCKS5 CONNECT reply ATYP {other} unsupported"
693 )));
694 }
695 }
696 Ok(())
697}
698
699async fn reply_socks5_connect(
703 stream: &mut TcpStream,
704 host: &str,
705 port: u16,
706) -> Result<(), ProtocolError> {
707 stream.write_all(&[0x05, 0x01, 0x00]).await?;
709 let parsed_host: std::net::IpAddr = host
711 .parse()
712 .unwrap_or(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED));
713 if let std::net::IpAddr::V4(ipv4) = parsed_host {
714 stream.write_all(&[0x01]).await?;
715 stream.write_all(&ipv4.octets()).await?;
716 } else if let std::net::IpAddr::V6(ipv6) = parsed_host {
717 stream.write_all(&[0x04]).await?;
718 stream.write_all(&ipv6.octets()).await?;
719 } else {
720 let bytes = host.as_bytes();
721 if bytes.len() > 255 {
722 return Err(ProtocolError::ConfigInvalid(format!(
723 "SOCKS5 target host too long: {}",
724 bytes.len()
725 )));
726 }
727 stream.write_all(&[0x03, bytes.len() as u8]).await?;
728 stream.write_all(bytes).await?;
729 }
730 stream.write_all(&port.to_be_bytes()).await?;
731 stream.flush().await?;
732 Ok(())
733}
734
735async fn relay_pproxy_pair(
740 external: TcpStream,
741 control: TcpStream,
742 _target: Option<(String, u16)>,
743 framing: PproxyBackwardFraming,
744 timeout_ms: u64,
745) -> Result<(), ProtocolError> {
746 let timeout = (timeout_ms > 0).then(|| Duration::from_millis(timeout_ms));
747 match framing {
748 PproxyBackwardFraming::Raw => {
749 relay_bidirectional_with_timeout(external, control, timeout).await
750 }
751 PproxyBackwardFraming::Socks5 => {
752 relay_bidirectional_with_timeout(external, control, timeout).await
753 }
754 }
755}
756
757async fn connect_jump(
758 mut stream: TcpStream,
759 hop: &eggress_uri::ProxyHopSpec,
760 target: &TargetAddr,
761 timeout_ms: u64,
762) -> Result<TcpStream, ProtocolError> {
763 match hop.protocols.as_slice() {
764 [ProtocolSpec::Http] | [ProtocolSpec::HttpOnly] => {
765 let authority = target.to_string();
766 let mut request = format!(
767 "CONNECT {authority} HTTP/1.1\r\nHost: {authority}\r\nConnection: keep-alive\r\n"
768 );
769 if let Some(credentials) = &hop.credentials {
770 let encoded = base64_encode(
771 format!("{}:{}", credentials.username, credentials.password).as_bytes(),
772 );
773 request.push_str(&format!("Proxy-Authorization: Basic {encoded}\r\n"));
774 }
775 request.push_str("\r\n");
776 stream.write_all(request.as_bytes()).await?;
777 let mut response = Vec::new();
778 read_until_headers(&mut stream, &mut response, timeout_ms).await?;
779 let status = response
780 .split(|byte| *byte == b' ')
781 .nth(1)
782 .and_then(|code| std::str::from_utf8(code).ok())
783 .and_then(|code| code.parse::<u16>().ok());
784 if status != Some(200) {
785 return Err(ProtocolError::ConfigInvalid(
786 "pproxy backward HTTP jump rejected CONNECT".into(),
787 ));
788 }
789 Ok(stream)
790 }
791 [ProtocolSpec::Socks5] => {
792 let credentials = hop.credentials.as_ref();
793 if credentials.is_some() {
794 stream.write_all(&[5, 1, 2]).await?;
795 } else {
796 stream.write_all(&[5, 1, 0]).await?;
797 }
798 let mut method = [0u8; 2];
799 stream.read_exact(&mut method).await?;
800 if method[0] != 5 || method[1] == 0xff {
801 return Err(ProtocolError::ConfigInvalid(
802 "pproxy backward SOCKS5 jump rejected authentication".into(),
803 ));
804 }
805 if method[1] == 2 {
806 let credentials = credentials.ok_or_else(|| {
807 ProtocolError::ConfigInvalid(
808 "SOCKS5 jump requested credentials that were not configured".into(),
809 )
810 })?;
811 let user = credentials.username.as_bytes();
812 let pass = credentials.password.as_bytes();
813 if user.len() > 255 || pass.len() > 255 {
814 return Err(ProtocolError::ConfigInvalid(
815 "SOCKS5 jump credentials are too long".into(),
816 ));
817 }
818 stream.write_all(&[1, user.len() as u8]).await?;
819 stream.write_all(user).await?;
820 stream.write_all(&[pass.len() as u8]).await?;
821 stream.write_all(pass).await?;
822 let mut auth_reply = [0u8; 2];
823 stream.read_exact(&mut auth_reply).await?;
824 if auth_reply != [1, 0] {
825 return Err(ProtocolError::AuthFailed);
826 }
827 }
828 let address = encode_socks_address(target)?;
829 stream.write_all(&[5, 1, 0]).await?;
830 stream.write_all(&address).await?;
831 let mut reply = [0u8; 4];
832 stream.read_exact(&mut reply).await?;
833 if reply[1] != 0 {
834 return Err(ProtocolError::ConfigInvalid(format!(
835 "SOCKS5 backward jump CONNECT failed with code {}",
836 reply[1]
837 )));
838 }
839 let remaining = match reply[3] {
840 1 => 6,
841 4 => 18,
842 3 => {
843 let mut length = [0u8; 1];
844 stream.read_exact(&mut length).await?;
845 usize::from(length[0]) + 2
846 }
847 _ => return Err(ProtocolError::ConfigInvalid("invalid SOCKS5 reply".into())),
848 };
849 let mut discard = vec![0u8; remaining];
850 stream.read_exact(&mut discard).await?;
851 Ok(stream)
852 }
853 _ => Err(ProtocolError::ConfigInvalid(
854 "pproxy backward jump supports only HTTP CONNECT and SOCKS5".into(),
855 )),
856 }
857}
858
859async fn read_until_headers(
860 stream: &mut TcpStream,
861 output: &mut Vec<u8>,
862 timeout_ms: u64,
863) -> Result<(), ProtocolError> {
864 let timeout = Duration::from_millis(timeout_ms.max(1));
865 let mut chunk = [0u8; 1024];
866 while output.len() < 16 * 1024 {
867 let read = tokio::time::timeout(timeout, stream.read(&mut chunk)).await;
868 let n = match read {
869 Ok(Ok(n)) => n,
870 Ok(Err(error)) => return Err(error.into()),
871 Err(_) => {
872 return Err(ProtocolError::ConfigInvalid(
873 "timed out reading proxy jump response headers".into(),
874 ));
875 }
876 };
877 if n == 0 {
878 return Err(ProtocolError::ConnectionClosed);
879 }
880 let scan_start = output.len().saturating_sub(3);
881 output.extend_from_slice(&chunk[..n]);
882 if let Some(index) = output[scan_start..]
883 .windows(4)
884 .position(|window| window == b"\r\n\r\n")
885 {
886 output.truncate(scan_start + index + 4);
889 return Ok(());
890 }
891 }
892 Err(ProtocolError::ConfigInvalid(
893 "proxy jump response headers exceed 16 KiB".into(),
894 ))
895}
896
897fn encode_socks_address(target: &TargetAddr) -> Result<Vec<u8>, ProtocolError> {
898 let mut output = Vec::new();
899 match &target.host {
900 TargetHost::Ip(std::net::IpAddr::V4(ip)) => {
901 output.push(1);
902 output.extend_from_slice(&ip.octets());
903 }
904 TargetHost::Ip(std::net::IpAddr::V6(ip)) => {
905 output.push(4);
906 output.extend_from_slice(&ip.octets());
907 }
908 TargetHost::Domain(domain) => {
909 if domain.len() > 255 {
910 return Err(ProtocolError::ConfigInvalid(
911 "SOCKS5 backward jump target domain is too long".into(),
912 ));
913 }
914 output.push(3);
915 output.push(domain.len() as u8);
916 output.extend_from_slice(domain.as_bytes());
917 }
918 }
919 output.extend_from_slice(&target.port.to_be_bytes());
920 Ok(output)
921}
922
923fn base64_encode(input: &[u8]) -> String {
924 const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
925 let mut output = String::new();
926 for chunk in input.chunks(3) {
927 let a = chunk[0];
928 let b = *chunk.get(1).unwrap_or(&0);
929 let c = *chunk.get(2).unwrap_or(&0);
930 output.push(TABLE[(a >> 2) as usize] as char);
931 output.push(TABLE[(((a << 4) | (b >> 4)) & 0x3f) as usize] as char);
932 output.push(if chunk.len() > 1 {
933 TABLE[(((b << 2) | (c >> 6)) & 0x3f) as usize] as char
934 } else {
935 '='
936 });
937 output.push(if chunk.len() > 2 {
938 TABLE[(c & 0x3f) as usize] as char
939 } else {
940 '='
941 });
942 }
943 output
944}
945
946#[cfg(test)]
947mod tests {
948 use super::*;
949 use crate::client::{TargetResolution, TargetResolver};
950
951 struct Resolver;
952 impl TargetResolver for Resolver {
953 fn resolve(&self) -> TargetResolution {
954 TargetResolution::Reject {
955 reason: "test".into(),
956 }
957 }
958 }
959
960 struct FixedResolver(SocketAddr);
961 impl TargetResolver for FixedResolver {
962 fn resolve(&self) -> TargetResolution {
963 TargetResolution::Connect {
964 host: self.0.ip().to_string(),
965 port: self.0.port(),
966 }
967 }
968 }
969
970 #[test]
971 fn raw_auth_is_not_newline_terminated() {
972 assert_eq!(raw_auth(Some("user"), Some("pass")), b"user:pass");
973 assert!(!raw_auth(Some("user"), Some("pass")).contains(&b'\n'));
974 }
975
976 #[tokio::test]
977 async fn client_cancellation_is_prompt() {
978 let client = PproxyBackwardClient::new(
979 PproxyBackwardClientConfig {
980 server_addr: "127.0.0.1:1".parse().unwrap(),
981 reconnect_initial_ms: 1,
982 reconnect_max_ms: 2,
983 ..Default::default()
984 },
985 Arc::new(Resolver),
986 );
987 let cancel = client.cancel_token();
988 let task = tokio::spawn(async move { client.run().await });
989 tokio::time::sleep(Duration::from_millis(5)).await;
990 cancel.cancel();
991 tokio::time::timeout(Duration::from_secs(1), task)
992 .await
993 .unwrap()
994 .unwrap()
995 .unwrap();
996 }
997
998 #[tokio::test]
999 async fn raw_backward_client_and_server_relay_without_native_handshake() {
1000 let target_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1001 let target_addr = target_listener.local_addr().unwrap();
1002 tokio::spawn(async move {
1003 let (mut stream, _) = target_listener.accept().await.unwrap();
1004 let mut buf = [0u8; 64];
1005 let size = stream.read(&mut buf).await.unwrap();
1006 stream.write_all(&buf[..size]).await.unwrap();
1007 });
1008
1009 let control_addr = TcpListener::bind("127.0.0.1:0")
1010 .await
1011 .unwrap()
1012 .local_addr()
1013 .unwrap();
1014 let external_addr = TcpListener::bind("127.0.0.1:0")
1015 .await
1016 .unwrap()
1017 .local_addr()
1018 .unwrap();
1019 let server = PproxyBackwardServer::new(PproxyBackwardServerConfig {
1020 control_bind: control_addr,
1021 external_bind: external_addr,
1022 auth: b"user:pass".to_vec(),
1023 read_timeout_ms: 2_000,
1024 ..Default::default()
1025 });
1026 let server_cancel = server.cancel_token();
1027 let server_task = tokio::spawn(server.run());
1028
1029 let client = PproxyBackwardClient::new(
1030 PproxyBackwardClientConfig {
1031 server_addr: control_addr,
1032 auth: b"user:pass".to_vec(),
1033 reconnect_initial_ms: 1,
1034 reconnect_max_ms: 5,
1035 ..Default::default()
1036 },
1037 Arc::new(FixedResolver(target_addr)),
1038 );
1039 let client_cancel = client.cancel_token();
1040 let client_task = tokio::spawn(async move { client.run().await });
1041
1042 let deadline = tokio::time::Instant::now() + Duration::from_secs(2);
1043 let mut external = loop {
1044 match TcpStream::connect(external_addr).await {
1045 Ok(stream) => break stream,
1046 Err(error) if tokio::time::Instant::now() < deadline => {
1047 debug!(%error, "waiting for backward external listener in test");
1048 tokio::time::sleep(Duration::from_millis(5)).await;
1049 }
1050 Err(error) => panic!("backward external listener did not start: {error}"),
1051 }
1052 };
1053 external.write_all(b"backward").await.unwrap();
1054 let mut echoed = [0u8; 8];
1055 tokio::time::timeout(Duration::from_secs(2), external.read_exact(&mut echoed))
1056 .await
1057 .unwrap()
1058 .unwrap();
1059 assert_eq!(&echoed, b"backward");
1060
1061 client_cancel.cancel();
1062 server_cancel.cancel();
1063 external.shutdown().await.unwrap();
1064 tokio::time::timeout(Duration::from_secs(2), client_task)
1065 .await
1066 .unwrap()
1067 .unwrap()
1068 .unwrap();
1069 tokio::time::timeout(Duration::from_secs(2), server_task)
1070 .await
1071 .unwrap()
1072 .unwrap()
1073 .unwrap();
1074 }
1075}