1use std::fmt;
6use std::pin::Pin;
7
8use anyhow::{bail, format_err, Context as _, Result};
9use base64::Engine;
10use bytes::{BufMut, BytesMut};
11use fast_socks5::client::Socks5Stream;
12use fast_socks5::util::target_addr::ToTargetAddr;
13use fast_socks5::AuthenticationMethod;
14use fast_socks5::Socks5Command;
15use percent_encoding::{percent_encode, utf8_percent_encode, NON_ALPHANUMERIC};
16use tokio::io::{AsyncReadExt, AsyncWriteExt};
17use tokio::net::TcpStream;
18use tokio_io_timeout::TimeoutStream;
19use url::Url;
20
21use crate::config::Config;
22use crate::constants::NON_ALPHANUMERIC_WITHOUT_DOT;
23use crate::context::Context;
24use crate::net::connect_tcp;
25use crate::net::session::SessionStream;
26use crate::net::tls::wrap_rustls;
27use crate::sql::Sql;
28
29pub const DEFAULT_SOCKS_PORT: u16 = 1080;
31
32#[derive(Debug, Clone)]
33pub struct ShadowsocksConfig {
34 pub server_config: shadowsocks::config::ServerConfig,
35}
36
37impl PartialEq for ShadowsocksConfig {
38 fn eq(&self, other: &Self) -> bool {
39 self.server_config.to_url() == other.server_config.to_url()
40 }
41}
42
43impl Eq for ShadowsocksConfig {}
44
45impl ShadowsocksConfig {
46 fn to_url(&self) -> String {
47 self.server_config.to_url()
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct HttpConfig {
53 pub host: String,
55
56 pub port: u16,
58
59 pub user_password: Option<(String, String)>,
63}
64
65impl HttpConfig {
66 fn from_url(url: Url) -> Result<Self> {
67 let host = url
68 .host_str()
69 .context("HTTP proxy URL has no host")?
70 .to_string();
71 let port = url
72 .port_or_known_default()
73 .context("HTTP(S) URLs are guaranteed to return Some port")?;
74 let user_password = if let Some(password) = url.password() {
75 let username = percent_encoding::percent_decode_str(url.username())
76 .decode_utf8()
77 .context("HTTP(S) proxy username is not a valid UTF-8")?
78 .to_string();
79 let password = percent_encoding::percent_decode_str(password)
80 .decode_utf8()
81 .context("HTTP(S) proxy password is not a valid UTF-8")?
82 .to_string();
83 Some((username, password))
84 } else {
85 None
86 };
87 let http_config = HttpConfig {
88 host,
89 port,
90 user_password,
91 };
92 Ok(http_config)
93 }
94
95 fn to_url(&self, scheme: &str) -> String {
96 let host = utf8_percent_encode(&self.host, NON_ALPHANUMERIC_WITHOUT_DOT);
97 if let Some((user, password)) = &self.user_password {
98 let user = utf8_percent_encode(user, NON_ALPHANUMERIC);
99 let password = utf8_percent_encode(password, NON_ALPHANUMERIC);
100 format!("{scheme}://{user}:{password}@{host}:{}", self.port)
101 } else {
102 format!("{scheme}://{host}:{}", self.port)
103 }
104 }
105}
106
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct Socks5Config {
109 pub host: String,
110 pub port: u16,
111 pub user_password: Option<(String, String)>,
112}
113
114impl Socks5Config {
115 async fn connect(
116 &self,
117 context: &Context,
118 target_host: &str,
119 target_port: u16,
120 load_dns_cache: bool,
121 ) -> Result<Socks5Stream<Pin<Box<TimeoutStream<TcpStream>>>>> {
122 let tcp_stream = connect_tcp(context, &self.host, self.port, load_dns_cache)
123 .await
124 .context("Failed to connect to SOCKS5 proxy")?;
125
126 let authentication_method = if let Some((username, password)) = self.user_password.as_ref()
127 {
128 Some(AuthenticationMethod::Password {
129 username: username.into(),
130 password: password.into(),
131 })
132 } else {
133 None
134 };
135 let mut socks_stream =
136 Socks5Stream::use_stream(tcp_stream, authentication_method, Default::default()).await?;
137 let target_addr = (target_host, target_port).to_target_addr()?;
138 socks_stream
139 .request(Socks5Command::TCPConnect, target_addr)
140 .await?;
141
142 Ok(socks_stream)
143 }
144
145 fn to_url(&self) -> String {
146 let host = utf8_percent_encode(&self.host, NON_ALPHANUMERIC_WITHOUT_DOT);
147 if let Some((user, password)) = &self.user_password {
148 let user = utf8_percent_encode(user, NON_ALPHANUMERIC);
149 let password = utf8_percent_encode(password, NON_ALPHANUMERIC);
150 format!("socks5://{user}:{password}@{host}:{}", self.port)
151 } else {
152 format!("socks5://{host}:{}", self.port)
153 }
154 }
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
161pub enum ProxyConfig {
162 Http(HttpConfig),
164
165 Https(HttpConfig),
167
168 Socks5(Socks5Config),
170
171 Shadowsocks(ShadowsocksConfig),
173}
174
175fn http_connect_request(host: &str, port: u16, auth: Option<(&str, &str)>) -> String {
177 let mut res = format!("CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n");
181 if let Some((username, password)) = auth {
182 res += "Proxy-Authorization: Basic ";
183 res += &base64::engine::general_purpose::STANDARD.encode(format!("{username}:{password}"));
184 res += "\r\n";
185 }
186 res += "\r\n";
187 res
188}
189
190async fn http_tunnel<T>(mut conn: T, host: &str, port: u16, auth: Option<(&str, &str)>) -> Result<T>
195where
196 T: AsyncReadExt + AsyncWriteExt + Unpin,
197{
198 let request = http_connect_request(host, port, auth);
200 conn.write_all(request.as_bytes()).await?;
201
202 let mut buffer = BytesMut::with_capacity(4096);
203
204 let res = loop {
205 if !buffer.has_remaining_mut() {
206 bail!("CONNECT response exceeded buffer size");
207 }
208 let n = conn.read_buf(&mut buffer).await?;
209 if n == 0 {
210 bail!("Unexpected end of CONNECT response");
211 }
212
213 let res = &buffer[..];
214 if res.ends_with(b"\r\n\r\n") {
215 break res;
217 }
218 };
219
220 if !res.starts_with(b"HTTP/") {
223 bail!("Unexpected HTTP CONNECT response: {res:?}");
224 }
225
226 let status_code = res
233 .get(9..12)
234 .context("HTTP status line does not contain a status code")?;
235
236 if status_code == b"407" {
239 Err(format_err!("Proxy Authentication Required"))
240 } else if status_code.starts_with(b"2") {
241 Ok(conn)
243 } else {
244 Err(format_err!(
245 "Failed to establish HTTP CONNECT tunnel: {res:?}"
246 ))
247 }
248}
249
250impl ProxyConfig {
251 pub fn from_url(url: &str) -> Result<Self> {
253 let url = Url::parse(url).context("Cannot parse proxy URL")?;
254 match url.scheme() {
255 "http" => {
256 let http_config = HttpConfig::from_url(url)?;
257 Ok(Self::Http(http_config))
258 }
259 "https" => {
260 let https_config = HttpConfig::from_url(url)?;
261 Ok(Self::Https(https_config))
262 }
263 "ss" => {
264 let server_config = shadowsocks::config::ServerConfig::from_url(url.as_str())?;
265 let shadowsocks_config = ShadowsocksConfig { server_config };
266 Ok(Self::Shadowsocks(shadowsocks_config))
267 }
268
269 "socks5" => {
278 let host = url
279 .host_str()
280 .context("socks5 URL has no host")?
281 .to_string();
282 let port = url.port().unwrap_or(DEFAULT_SOCKS_PORT);
283 let user_password = if let Some(password) = url.password() {
284 let username = percent_encoding::percent_decode_str(url.username())
285 .decode_utf8()
286 .context("SOCKS5 username is not a valid UTF-8")?
287 .to_string();
288 let password = percent_encoding::percent_decode_str(password)
289 .decode_utf8()
290 .context("SOCKS5 password is not a valid UTF-8")?
291 .to_string();
292 Some((username, password))
293 } else {
294 None
295 };
296 let socks5_config = Socks5Config {
297 host,
298 port,
299 user_password,
300 };
301 Ok(Self::Socks5(socks5_config))
302 }
303 scheme => Err(format_err!("Unknown URL scheme {scheme:?}")),
304 }
305 }
306
307 pub fn to_url(&self) -> String {
312 match self {
313 Self::Http(http_config) => http_config.to_url("http"),
314 Self::Https(http_config) => http_config.to_url("https"),
315 Self::Socks5(socks5_config) => socks5_config.to_url(),
316 Self::Shadowsocks(shadowsocks_config) => shadowsocks_config.to_url(),
317 }
318 }
319
320 async fn migrate_socks_config(sql: &Sql) -> Result<()> {
325 if sql.get_raw_config("proxy_url").await?.is_none() {
326 if let Some(host) = sql
328 .get_raw_config("socks5_host")
329 .await?
330 .filter(|s| !s.is_empty())
331 {
332 let port: u16 = sql
333 .get_raw_config_int("socks5_port")
334 .await?
335 .unwrap_or(DEFAULT_SOCKS_PORT.into()) as u16;
336 let user = sql.get_raw_config("socks5_user").await?.unwrap_or_default();
337 let pass = sql
338 .get_raw_config("socks5_password")
339 .await?
340 .unwrap_or_default();
341
342 let mut proxy_url = "socks5://".to_string();
343 if !pass.is_empty() {
344 proxy_url += &percent_encode(user.as_bytes(), NON_ALPHANUMERIC).to_string();
345 proxy_url += ":";
346 proxy_url += &percent_encode(pass.as_bytes(), NON_ALPHANUMERIC).to_string();
347 proxy_url += "@";
348 };
349 proxy_url += &host;
350 proxy_url += ":";
351 proxy_url += &port.to_string();
352
353 sql.set_raw_config("proxy_url", Some(&proxy_url)).await?;
354 } else {
355 sql.set_raw_config("proxy_url", Some("")).await?;
356 }
357
358 let socks5_enabled = sql.get_raw_config("socks5_enabled").await?;
359 sql.set_raw_config("proxy_enabled", socks5_enabled.as_deref())
360 .await?;
361 }
362
363 sql.set_raw_config("socks5_enabled", None).await?;
364 sql.set_raw_config("socks5_host", None).await?;
365 sql.set_raw_config("socks5_port", None).await?;
366 sql.set_raw_config("socks5_user", None).await?;
367 sql.set_raw_config("socks5_password", None).await?;
368 Ok(())
369 }
370
371 pub async fn load(context: &Context) -> Result<Option<Self>> {
373 Self::migrate_socks_config(&context.sql)
374 .await
375 .context("Failed to migrate legacy SOCKS config")?;
376
377 let enabled = context.get_config_bool(Config::ProxyEnabled).await?;
378 if !enabled {
379 return Ok(None);
380 }
381
382 let proxy_url = context
383 .get_config(Config::ProxyUrl)
384 .await?
385 .unwrap_or_default();
386 let proxy_url = proxy_url
387 .split_once('\n')
388 .map_or(proxy_url.clone(), |(first_url, _rest)| {
389 first_url.to_string()
390 });
391 let proxy_config = Self::from_url(&proxy_url).context("Failed to parse proxy URL")?;
392 Ok(Some(proxy_config))
393 }
394
395 pub(crate) async fn connect(
398 &self,
399 context: &Context,
400 target_host: &str,
401 target_port: u16,
402 load_dns_cache: bool,
403 ) -> Result<Box<dyn SessionStream>> {
404 match self {
405 ProxyConfig::Http(http_config) => {
406 let load_cache = false;
407 let tcp_stream = crate::net::connect_tcp(
408 context,
409 &http_config.host,
410 http_config.port,
411 load_cache,
412 )
413 .await?;
414 let auth = if let Some((username, password)) = &http_config.user_password {
415 Some((username.as_str(), password.as_str()))
416 } else {
417 None
418 };
419 let tunnel_stream = http_tunnel(tcp_stream, target_host, target_port, auth).await?;
420 Ok(Box::new(tunnel_stream))
421 }
422 ProxyConfig::Https(https_config) => {
423 let load_cache = true;
424 let tcp_stream = crate::net::connect_tcp(
425 context,
426 &https_config.host,
427 https_config.port,
428 load_cache,
429 )
430 .await?;
431 let tls_stream = wrap_rustls(&https_config.host, &[], tcp_stream).await?;
432 let auth = if let Some((username, password)) = &https_config.user_password {
433 Some((username.as_str(), password.as_str()))
434 } else {
435 None
436 };
437 let tunnel_stream = http_tunnel(tls_stream, target_host, target_port, auth).await?;
438 Ok(Box::new(tunnel_stream))
439 }
440 ProxyConfig::Socks5(socks5_config) => {
441 let socks5_stream = socks5_config
442 .connect(context, target_host, target_port, load_dns_cache)
443 .await?;
444 Ok(Box::new(socks5_stream))
445 }
446 ProxyConfig::Shadowsocks(ShadowsocksConfig { server_config }) => {
447 let shadowsocks_context = shadowsocks::context::Context::new_shared(
448 shadowsocks::config::ServerType::Local,
449 );
450
451 let tcp_stream = {
452 let server_addr = server_config.addr();
453 let host = server_addr.host();
454 let port = server_addr.port();
455 connect_tcp(context, &host, port, load_dns_cache)
456 .await
457 .context("Failed to connect to Shadowsocks proxy")?
458 };
459
460 let shadowsocks_stream = shadowsocks::ProxyClientStream::from_stream(
461 shadowsocks_context,
462 tcp_stream,
463 server_config,
464 (target_host.to_string(), target_port),
465 );
466
467 Ok(Box::new(shadowsocks_stream))
468 }
469 }
470 }
471}
472
473impl fmt::Display for Socks5Config {
474 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
475 write!(
476 f,
477 "host:{},port:{},user_password:{}",
478 self.host,
479 self.port,
480 if let Some(user_password) = self.user_password.clone() {
481 format!("user: {}, password: ***", user_password.0)
482 } else {
483 "user: None".to_string()
484 }
485 )
486 }
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492 use crate::config::Config;
493 use crate::test_utils::TestContext;
494
495 #[test]
496 fn test_socks5_url() {
497 let proxy_config = ProxyConfig::from_url("socks5://127.0.0.1:9050").unwrap();
498 assert_eq!(
499 proxy_config,
500 ProxyConfig::Socks5(Socks5Config {
501 host: "127.0.0.1".to_string(),
502 port: 9050,
503 user_password: None
504 })
505 );
506
507 let proxy_config = ProxyConfig::from_url("socks5://foo:bar@127.0.0.1:9150").unwrap();
508 assert_eq!(
509 proxy_config,
510 ProxyConfig::Socks5(Socks5Config {
511 host: "127.0.0.1".to_string(),
512 port: 9150,
513 user_password: Some(("foo".to_string(), "bar".to_string()))
514 })
515 );
516
517 let proxy_config = ProxyConfig::from_url("socks5://%66oo:b%61r@127.0.0.1:9150").unwrap();
518 assert_eq!(
519 proxy_config,
520 ProxyConfig::Socks5(Socks5Config {
521 host: "127.0.0.1".to_string(),
522 port: 9150,
523 user_password: Some(("foo".to_string(), "bar".to_string()))
524 })
525 );
526
527 let proxy_config = ProxyConfig::from_url("socks5://127.0.0.1:80").unwrap();
528 assert_eq!(
529 proxy_config,
530 ProxyConfig::Socks5(Socks5Config {
531 host: "127.0.0.1".to_string(),
532 port: 80,
533 user_password: None
534 })
535 );
536
537 let proxy_config = ProxyConfig::from_url("socks5://127.0.0.1").unwrap();
538 assert_eq!(
539 proxy_config,
540 ProxyConfig::Socks5(Socks5Config {
541 host: "127.0.0.1".to_string(),
542 port: 1080,
543 user_password: None
544 })
545 );
546
547 let proxy_config = ProxyConfig::from_url("socks5://127.0.0.1:1080").unwrap();
548 assert_eq!(
549 proxy_config,
550 ProxyConfig::Socks5(Socks5Config {
551 host: "127.0.0.1".to_string(),
552 port: 1080,
553 user_password: None
554 })
555 );
556 }
557
558 #[test]
559 fn test_http_url() {
560 let proxy_config = ProxyConfig::from_url("http://127.0.0.1").unwrap();
561 assert_eq!(
562 proxy_config,
563 ProxyConfig::Http(HttpConfig {
564 host: "127.0.0.1".to_string(),
565 port: 80,
566 user_password: None
567 })
568 );
569
570 let proxy_config = ProxyConfig::from_url("http://127.0.0.1:80").unwrap();
571 assert_eq!(
572 proxy_config,
573 ProxyConfig::Http(HttpConfig {
574 host: "127.0.0.1".to_string(),
575 port: 80,
576 user_password: None
577 })
578 );
579
580 let proxy_config = ProxyConfig::from_url("http://127.0.0.1:443").unwrap();
581 assert_eq!(
582 proxy_config,
583 ProxyConfig::Http(HttpConfig {
584 host: "127.0.0.1".to_string(),
585 port: 443,
586 user_password: None
587 })
588 );
589 }
590
591 #[test]
592 fn test_https_url() {
593 let proxy_config = ProxyConfig::from_url("https://127.0.0.1").unwrap();
594 assert_eq!(
595 proxy_config,
596 ProxyConfig::Https(HttpConfig {
597 host: "127.0.0.1".to_string(),
598 port: 443,
599 user_password: None
600 })
601 );
602
603 let proxy_config = ProxyConfig::from_url("https://127.0.0.1:80").unwrap();
604 assert_eq!(
605 proxy_config,
606 ProxyConfig::Https(HttpConfig {
607 host: "127.0.0.1".to_string(),
608 port: 80,
609 user_password: None
610 })
611 );
612
613 let proxy_config = ProxyConfig::from_url("https://127.0.0.1:443").unwrap();
614 assert_eq!(
615 proxy_config,
616 ProxyConfig::Https(HttpConfig {
617 host: "127.0.0.1".to_string(),
618 port: 443,
619 user_password: None
620 })
621 );
622 }
623
624 #[test]
625 fn test_http_connect_request() {
626 assert_eq!(http_connect_request("example.org", 143, Some(("aladdin", "opensesame"))), "CONNECT example.org:143 HTTP/1.1\r\nHost: example.org:143\r\nProxy-Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l\r\n\r\n");
627 assert_eq!(
628 http_connect_request("example.net", 587, None),
629 "CONNECT example.net:587 HTTP/1.1\r\nHost: example.net:587\r\n\r\n"
630 );
631 }
632
633 #[test]
634 fn test_shadowsocks_url() {
635 let proxy_config =
637 ProxyConfig::from_url("ss://YWVzLTEyOC1nY206dGVzdA@192.168.100.1:8888#Example1")
638 .unwrap();
639 assert!(matches!(proxy_config, ProxyConfig::Shadowsocks(_)));
640 }
641
642 #[test]
643 fn test_invalid_proxy_url() {
644 assert!(ProxyConfig::from_url("foobar://127.0.0.1:9050").is_err());
645 assert!(ProxyConfig::from_url("abc").is_err());
646
647 assert!(ProxyConfig::from_url("ss://foo:bar@127.0.0.1:9999").is_err());
649 }
650
651 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
652 async fn test_socks5_migration() -> Result<()> {
653 let t = TestContext::new().await;
654
655 t.set_config(Config::Socks5Host, Some("127.0.0.1")).await?;
657 t.set_config(Config::Socks5Port, Some("9050")).await?;
658
659 let proxy_config = ProxyConfig::load(&t).await?;
660 assert_eq!(proxy_config, None);
662
663 assert_eq!(
664 t.get_config(Config::ProxyUrl).await?.unwrap(),
665 "socks5://127.0.0.1:9050"
666 );
667 Ok(())
668 }
669
670 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
672 async fn test_socks5_migration_unconfigured() -> Result<()> {
673 let t = TestContext::new().await;
674
675 assert_eq!(ProxyConfig::load(&t).await?, None);
677
678 assert_eq!(t.get_config(Config::ProxyEnabled).await?, None);
679 assert_eq!(
680 t.get_config(Config::ProxyUrl).await?.unwrap(),
681 String::new()
682 );
683 Ok(())
684 }
685
686 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
688 async fn test_socks5_migration_empty() -> Result<()> {
689 let t = TestContext::new().await;
690
691 t.set_config(Config::Socks5Host, Some("")).await?;
692
693 assert_eq!(ProxyConfig::load(&t).await?, None);
695
696 assert_eq!(t.get_config(Config::ProxyEnabled).await?, None);
697 assert_eq!(
698 t.get_config(Config::ProxyUrl).await?.unwrap(),
699 String::new()
700 );
701 Ok(())
702 }
703}