|
1 |
| -use eyre::{Context, ContextCompat, Result}; |
| 1 | +use eyre::{bail, Context, ContextCompat, Result}; |
2 | 2 | use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
|
3 | 3 | use tokio::net::TcpStream;
|
| 4 | +use tokio_socks::tcp::Socks5Stream; |
| 5 | +use tracing::info; |
4 | 6 | use url::Url;
|
5 | 7 |
|
6 | 8 | use crate::rsync::downloader::Downloader;
|
@@ -36,19 +38,66 @@ pub struct TaskBuilders {
|
36 | 38 | pub progress: ProgressDisplay,
|
37 | 39 | }
|
38 | 40 |
|
| 41 | +async fn connect_with_proxy(target: &str) -> Result<TcpStream> { |
| 42 | + let proxy = std::env::var("SOCKS5_PROXY") |
| 43 | + .ok() |
| 44 | + .and_then(|s| (!s.is_empty()).then_some(s)) |
| 45 | + .or_else(|| { |
| 46 | + std::env::var("socks5_proxy") |
| 47 | + .ok() |
| 48 | + .and_then(|s| (!s.is_empty()).then_some(s)) |
| 49 | + }); |
| 50 | + |
| 51 | + if let Some(proxy) = proxy { |
| 52 | + let proxy = Url::parse(&proxy).context("invalid proxy URL")?; |
| 53 | + if proxy.scheme().to_lowercase() != "socks5" { |
| 54 | + bail!("unsupported proxy scheme: {}", proxy.scheme()); |
| 55 | + } |
| 56 | + let proxy_addr = proxy.host_str().context("missing proxy host")?; |
| 57 | + let proxy_port = proxy.port().unwrap_or(1080); |
| 58 | + let proxy_username = proxy.username(); |
| 59 | + let proxy_password = proxy.password().unwrap_or_default(); |
| 60 | + |
| 61 | + let stream = if proxy_username.is_empty() { |
| 62 | + info!("connecting to {} via SOCKS5 proxy {}", target, proxy); |
| 63 | + Socks5Stream::connect((proxy_addr, proxy_port), target) |
| 64 | + .await |
| 65 | + .context("proxy or rsync server refused connection. Are they running?")? |
| 66 | + } else { |
| 67 | + info!( |
| 68 | + "connecting to {} via SOCKS5 proxy {} as {}", |
| 69 | + target, proxy, proxy_username |
| 70 | + ); |
| 71 | + Socks5Stream::connect_with_password( |
| 72 | + (proxy_addr, proxy_port), |
| 73 | + target, |
| 74 | + proxy_username, |
| 75 | + proxy_password, |
| 76 | + ) |
| 77 | + .await |
| 78 | + .context("proxy or rsync server refused connection. Are they running?")? |
| 79 | + }; |
| 80 | + |
| 81 | + Ok(stream.into_inner()) |
| 82 | + } else { |
| 83 | + TcpStream::connect(target) |
| 84 | + .await |
| 85 | + .context("rsync server refused connection. Is it running?") |
| 86 | + } |
| 87 | +} |
| 88 | + |
39 | 89 | pub async fn start_handshake(url: &Url) -> Result<HandshakeConn> {
|
40 | 90 | let port = url.port().unwrap_or(873);
|
41 | 91 | let path = url.path().trim_start_matches('/');
|
42 | 92 | let auth = Auth::from_url_and_env(url);
|
43 | 93 | let module = path.split('/').next().context("empty remote path")?;
|
44 | 94 |
|
45 |
| - let stream = TcpStream::connect(format!( |
| 95 | + let stream = connect_with_proxy(&format!( |
46 | 96 | "{}:{}",
|
47 | 97 | url.host_str().context("missing remote host")?,
|
48 | 98 | port
|
49 | 99 | ))
|
50 |
| - .await |
51 |
| - .context("rsync server refused connection. Is it running?")?; |
| 100 | + .await?; |
52 | 101 |
|
53 | 102 | let mut handshake = HandshakeConn::new(stream);
|
54 | 103 | handshake.start_inband_exchange(module, path, auth).await?;
|
|
0 commit comments