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