├── .gitignore ├── .rustfmt.toml ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── client.rs ├── custom_auth_server.rs ├── router.rs ├── server.rs └── udp_client.rs ├── src ├── client.rs ├── lib.rs ├── server.rs ├── socks4 │ ├── client.rs │ └── mod.rs └── util │ ├── mod.rs │ ├── stream.rs │ └── target_addr.rs ├── test_udp.py └── tests └── sock5_client_test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea/ 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fast-socks5" 3 | version = "1.0.0-rc.0" 4 | authors = ["Jonathan Dizdarevic "] 5 | edition = "2021" 6 | license = "MIT" 7 | description = "Fast SOCKS5 client/server implementation written in Rust async/.await (tokio)" 8 | repository = "https://github.com/dizda/fast-socks5" 9 | categories = [ 10 | "asynchronous", 11 | "network-programming", 12 | "command-line-utilities", 13 | "authentication", 14 | ] 15 | keywords = ["io", "proxy", "vpn", "async", "socks"] 16 | 17 | [features] 18 | default = [] 19 | socks4 = [] 20 | 21 | [dependencies] 22 | log = "0.4" 23 | tokio = { version = "1", features = ["io-util", "net", "time", "macros"] } 24 | anyhow = "1" 25 | thiserror = "1" 26 | tokio-stream = "0.1" 27 | async-trait = "0.1" 28 | socket2 = "0.5.8" 29 | 30 | # Dependencies for examples and tests 31 | [dev-dependencies] 32 | env_logger = "0.9" 33 | structopt = "0.3" 34 | tokio = { version = "1", features = [ 35 | "io-util", 36 | "net", 37 | "time", 38 | "rt-multi-thread", 39 | "macros", 40 | ] } 41 | tokio-test = "0.4" 42 | 43 | [[example]] 44 | name = "server" 45 | 46 | [[example]] 47 | name = "client" 48 | 49 | [[example]] 50 | name = "custom_auth_server" 51 | 52 | [[example]] 53 | name = "router" 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Jonathan Dizdarevic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License](https://img.shields.io/github/license/dizda/fast-socks5.svg)](https://github.com/dizda/fast-socks5) 2 | [![crates.io](https://img.shields.io/crates/v/fast-socks5.svg)](https://crates.io/crates/fast-socks5) 3 | [![dependency status](https://deps.rs/repo/github/dizda/fast-socks5/status.svg)](https://deps.rs/repo/github/dizda/fast-socks5) 4 | [![Release](https://img.shields.io/github/release/dizda/fast-socks5.svg)](https://github.com/dizda/fast-socks5/releases) 5 | 6 |

7 |
8 | anyIP Proxy 9 |
10 |

11 | 12 |

Fast SOCKS5 TCP and UDP client/server library for Rust async (Tokio)

13 | 14 | --- 15 | 16 | 17 | ## Features 18 | 19 | - An `async`/`.await` [SOCKS5](https://tools.ietf.org/html/rfc1928) implementation. 20 | - An `async`/`.await` [SOCKS4 Client](https://www.openssh.com/txt/socks4.protocol) implementation. 21 | - An `async`/`.await` [SOCKS4a Client](https://www.openssh.com/txt/socks4a.protocol) implementation. 22 | - No **unsafe** code 23 | - Built on top of the [Tokio](https://tokio.rs/) runtime 24 | - Ultra lightweight and scalable 25 | - No system dependencies 26 | - Cross-platform 27 | - Infinitely extensible, explicit server API based on typestates for safety 28 | - You control the request handling, the library only ensures you follow the proper protocol flow 29 | - Can skip DNS resolution 30 | - Can skip the authentication/handshake process (not RFC-compliant, for private use, to save on useless round-trips) 31 | - Instead of proxying in-process, swap out `run_tcp_proxy` for custom handling to build a router or to use a custom accelerated proxying method 32 | - Authentication methods: 33 | - No-Auth method (`0x00`) 34 | - Username/Password auth method (`0x02`) 35 | - Custom auth methods can be implemented on the server side via the `AuthMethod` Trait 36 | - Multiple auth methods with runtime negotiation can be supported, with fast *static* dispatch (enums can be generated with the `auth_method_enums` macro) 37 | - UDP is supported 38 | - All SOCKS5 RFC errors (replies) should be mapped 39 | - `IPv4`, `IPv6`, and `Domains` types are supported 40 | - Exhaustive [`examples`](https://github.com/dizda/fast-socks5/tree/master/examples) are provided that can be run immediately: 41 | - client 42 | - server 43 | - custom_auth_server 44 | - router 45 | - udp_client 46 | 47 | 48 | This library is maintained by [anyip.io](https://anyip.io/) a residential and mobile socks5 proxy provider. 49 | 50 | ## Install 51 | 52 | Open in [crates.io](https://crates.io/crates/fast-socks5). 53 | 54 | 55 | ## Examples 56 | 57 | Please check [`examples`](https://github.com/dizda/fast-socks5/tree/master/examples) directory. 58 | 59 | ```bash 60 | # Run client 61 | RUST_LOG=debug cargo run --example client -- --socks-server 127.0.0.1:1337 --username admin --password password -a perdu.com -p 80 62 | 63 | # Run server 64 | RUST_LOG=debug cargo run --example server -- --listen-addr 127.0.0.1:1337 password -u admin -p password 65 | 66 | # Test it with cURL 67 | curl -v --proxy socks5://admin:password@127.0.0.1:1337 https://ipapi.co/json/ 68 | ``` 69 | 70 | 71 | ## Inspired by 72 | 73 | Thanks to all these SOCKS5 projects 74 | 75 | - https://github.com/sfackler/rust-socks/blob/master/src/v5.rs 76 | - https://github.com/shadowsocks/shadowsocks-rust/blob/master/src/relay/socks5.rs 77 | - https://github.com/ylxdzsw/v2socks/blob/master/src/socks.rs 78 | 79 | ## Further Considerations 80 | 81 | - Implementation made with Tokio-codec https://github.com/yfaming/yimu-rs/blob/master/src/socks5.rs 82 | -------------------------------------------------------------------------------- /examples/client.rs: -------------------------------------------------------------------------------- 1 | #[forbid(unsafe_code)] 2 | #[macro_use] 3 | extern crate log; 4 | 5 | use anyhow::Context; 6 | use fast_socks5::client::Config; 7 | use fast_socks5::{client::Socks5Stream, Result}; 8 | use structopt::StructOpt; 9 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 10 | 11 | /// # How to use it: 12 | /// 13 | /// GET / of web server by IPv4 address: 14 | /// `$ RUST_LOG=debug cargo run --example client -- --socks-server 127.0.0.1:1337 --username admin --password password -a 208.97.177.124 -p 80` 15 | /// 16 | /// GET / of web server by IPv6 address: 17 | /// `$ RUST_LOG=debug cargo run --example client -- --socks-server 127.0.0.1:1337 --username admin --password password -a ::ffff:208.97.177.124 -p 80` 18 | /// 19 | /// GET / of web server by domain name: 20 | /// `$ RUST_LOG=debug cargo run --example client -- --socks-server 127.0.0.1:1337 --username admin --password password -a perdu.com -p 80` 21 | /// 22 | #[derive(Debug, StructOpt)] 23 | #[structopt(name = "socks5-client", about = "A simple example of a socks5-client.")] 24 | struct Opt { 25 | /// Socks5 server address + port. eg. `127.0.0.1:1080` 26 | #[structopt(short, long)] 27 | pub socks_server: String, 28 | 29 | /// Target address server (not the socks server) 30 | #[structopt(short = "a", long)] 31 | pub target_addr: String, 32 | 33 | /// Target port server (not the socks server) 34 | #[structopt(short = "p", long)] 35 | pub target_port: u16, 36 | 37 | #[structopt(short, long)] 38 | pub username: Option, 39 | 40 | #[structopt(long)] 41 | pub password: Option, 42 | 43 | /// Don't perform the auth handshake, send directly the command request 44 | #[structopt(short = "k", long)] 45 | pub skip_auth: bool, 46 | } 47 | 48 | #[tokio::main] 49 | async fn main() -> Result<()> { 50 | env_logger::init(); 51 | 52 | spawn_socks_client().await 53 | } 54 | 55 | async fn spawn_socks_client() -> Result<()> { 56 | let opt: Opt = Opt::from_args(); 57 | let domain = opt.target_addr.clone(); 58 | let mut config = Config::default(); 59 | config.set_skip_auth(opt.skip_auth); 60 | 61 | // Creating a SOCKS stream to the target address through the socks server 62 | let mut socks = match opt.username { 63 | Some(username) => Socks5Stream::connect_with_password( 64 | opt.socks_server, 65 | opt.target_addr, 66 | opt.target_port, 67 | username, 68 | opt.password.expect("Please fill the password"), 69 | config, 70 | ) 71 | .await?, 72 | 73 | _ => Socks5Stream::connect(opt.socks_server, opt.target_addr, opt.target_port, config) 74 | .await? 75 | }; 76 | 77 | // Once connection is completed, can start to communicate with the server 78 | http_request(&mut socks, domain).await?; 79 | 80 | Ok(()) 81 | } 82 | 83 | /// Simple HTTP request 84 | async fn http_request( 85 | stream: &mut T, 86 | domain: String, 87 | ) -> Result<()> { 88 | debug!("Requesting body..."); 89 | 90 | // construct our request, with a dynamic domain 91 | let mut headers = vec![]; 92 | headers.extend_from_slice("GET / HTTP/1.1\r\nHost: ".as_bytes()); 93 | headers.extend_from_slice(domain.as_bytes()); 94 | headers 95 | .extend_from_slice("\r\nUser-Agent: fast-socks5/0.1.0\r\nAccept: */*\r\n\r\n".as_bytes()); 96 | 97 | // flush headers 98 | stream 99 | .write_all(&headers) 100 | .await 101 | .context("Can't write HTTP Headers")?; 102 | 103 | debug!("Reading body response..."); 104 | let mut result = [0u8; 1024]; 105 | // warning: read_to_end() method sometimes await forever when the web server 106 | // doesn't write EOF char (\r\n\r\n). 107 | // read() seems more appropriate 108 | stream 109 | .read(&mut result) 110 | .await 111 | .context("Can't read HTTP Response")?; 112 | 113 | info!("Response: {}", String::from_utf8_lossy(&result)); 114 | 115 | if result.starts_with(b"HTTP/1.1") { 116 | info!("HTTP/1.1 Response detected!"); 117 | } 118 | //assert!(result.ends_with(b"\r\n") || result.ends_with(b"")); 119 | 120 | Ok(()) 121 | } 122 | -------------------------------------------------------------------------------- /examples/custom_auth_server.rs: -------------------------------------------------------------------------------- 1 | #[forbid(unsafe_code)] 2 | #[macro_use] 3 | extern crate log; 4 | 5 | use fast_socks5::{ 6 | auth_method_enums, 7 | server::{ 8 | run_tcp_proxy, AuthMethod, AuthMethodSuccessState, DnsResolveHelper as _, 9 | PasswordAuthentication, PasswordAuthenticationStarted, Socks5ServerProtocol, 10 | }, 11 | ReplyError, Result, Socks5Command, SocksError, 12 | }; 13 | use std::{future::Future, time::Duration}; 14 | use structopt::StructOpt; 15 | use tokio::task; 16 | use tokio::{ 17 | io::{AsyncRead, AsyncReadExt}, 18 | net::TcpListener, 19 | }; 20 | 21 | /// # How to use it: 22 | /// 23 | /// Listen on a local address: 24 | /// `$ RUST_LOG=debug cargo run --example custom_auth_server -- --listen-addr 127.0.0.1:1337` 25 | /// 26 | /// then try a client to connect to this server: 27 | /// `$ RUST_LOG=debug cargo run --example client -- --socks-server 127.0.0.1:1337 --username user --password "correct_horse_battery_staple" -a perdu.com -p 80` 28 | /// 29 | /// or via a cURL command 30 | /// `curl -v -s --proxy "socks5://user:correct_horse_battery_staple@127.0.0.1:1337" "https://httpbin.org/get"` 31 | /// 32 | #[derive(Debug, StructOpt)] 33 | #[structopt( 34 | name = "socks5-server-custom-auth", 35 | about = "A socks5 server with a curious secret." 36 | )] 37 | struct Opt { 38 | /// Bind on address address. eg. `127.0.0.1:1080` 39 | #[structopt(short, long)] 40 | pub listen_addr: String, 41 | } 42 | 43 | #[tokio::main] 44 | async fn main() -> Result<()> { 45 | env_logger::init(); 46 | 47 | spawn_socks_server().await 48 | } 49 | 50 | async fn spawn_socks_server() -> Result<()> { 51 | let opt: Opt = Opt::from_args(); 52 | 53 | let listener = TcpListener::bind(&opt.listen_addr).await?; 54 | 55 | info!("Listen for socks connections @ {}", &opt.listen_addr); 56 | 57 | // Standard TCP loop 58 | loop { 59 | match listener.accept().await { 60 | Ok((socket, _client_addr)) => { 61 | spawn_and_log_error(serve_socks5(socket)); 62 | } 63 | Err(err) => { 64 | error!("accept error = {:?}", err); 65 | } 66 | } 67 | } 68 | } 69 | 70 | pub struct BackdoorAuthenticationStarted(T); 71 | pub struct BackdoorAuthenticationSuccess(T); 72 | 73 | impl BackdoorAuthenticationStarted { 74 | pub async fn verify_timing(self) -> Result> { 75 | let mut socket = self.0; 76 | let mut buf = vec![0u8; 2]; 77 | if tokio::time::timeout(Duration::from_millis(500), socket.read_exact(&mut buf)) 78 | .await 79 | .is_ok() 80 | { 81 | debug!("too early!"); 82 | return Err(SocksError::AuthenticationRejected("nope".to_owned())); 83 | } 84 | if tokio::time::timeout(Duration::from_millis(500), socket.read_exact(&mut buf)) 85 | .await 86 | .is_err() 87 | { 88 | debug!("too late!"); 89 | return Err(SocksError::AuthenticationRejected("nope".to_owned())); 90 | } 91 | if buf[0] == 0x13 && buf[1] == 0x37 { 92 | Ok(BackdoorAuthenticationSuccess(socket)) 93 | } else { 94 | debug!("wrong contents!"); 95 | Err(SocksError::AuthenticationRejected("nope".to_owned())) 96 | } 97 | } 98 | } 99 | 100 | impl AuthMethodSuccessState for BackdoorAuthenticationSuccess { 101 | fn into_inner(self) -> T { 102 | self.0 103 | } 104 | } 105 | 106 | /// A silly example of a custom authentication method. 107 | #[derive(Debug, Clone, Copy)] 108 | pub struct BackdoorAuthentication; 109 | 110 | impl AuthMethod for BackdoorAuthentication { 111 | type StartingState = BackdoorAuthenticationStarted; 112 | 113 | fn method_id(self) -> u8 { 114 | 0xF0 // From the "RESERVED FOR PRIVATE METHODS" range 115 | } 116 | 117 | fn new(self, inner: T) -> Self::StartingState { 118 | BackdoorAuthenticationStarted(inner) 119 | } 120 | } 121 | 122 | auth_method_enums! { 123 | pub enum Auth / AuthStarted { 124 | PasswordAuthentication(PasswordAuthenticationStarted), 125 | BackdoorAuthentication(BackdoorAuthenticationStarted), 126 | } 127 | } 128 | 129 | async fn serve_socks5(socket: tokio::net::TcpStream) -> Result<(), SocksError> { 130 | let proto = match Socks5ServerProtocol::start(socket) 131 | .negotiate_auth(&[ 132 | // The order of authentication methods can be tested by clients in sequence, 133 | // so list more secure or preferred methods first 134 | Auth::PasswordAuthentication(PasswordAuthentication), 135 | Auth::BackdoorAuthentication(BackdoorAuthentication), 136 | ]) 137 | .await? 138 | { 139 | AuthStarted::PasswordAuthentication(auth) => { 140 | let (user, pass, auth) = auth.read_username_password().await?; 141 | if user == "user" && pass == "correct_horse_battery_staple" { 142 | // better to not use spaces for trying out with cURL 143 | auth.accept().await?.finish_auth() 144 | } else { 145 | auth.reject().await?; 146 | return Err(SocksError::AuthenticationRejected( 147 | "Wrong username/password".to_owned(), 148 | )); 149 | } 150 | } 151 | AuthStarted::BackdoorAuthentication(auth) => auth.verify_timing().await?.finish_auth(), 152 | }; 153 | 154 | let (proto, cmd, target_addr) = proto.read_command().await?.resolve_dns().await?; 155 | 156 | const REQUEST_TIMEOUT: u64 = 10; 157 | match cmd { 158 | Socks5Command::TCPConnect => { 159 | run_tcp_proxy(proto, &target_addr, REQUEST_TIMEOUT, false).await?; 160 | } 161 | _ => { 162 | proto.reply_error(&ReplyError::CommandNotSupported).await?; 163 | return Err(ReplyError::CommandNotSupported.into()); 164 | } 165 | }; 166 | Ok(()) 167 | } 168 | 169 | fn spawn_and_log_error(fut: F) -> task::JoinHandle<()> 170 | where 171 | F: Future> + Send + 'static, 172 | { 173 | task::spawn(async move { 174 | match fut.await { 175 | Ok(()) => {} 176 | Err(err) => error!("{:#}", &err), 177 | } 178 | }) 179 | } 180 | -------------------------------------------------------------------------------- /examples/router.rs: -------------------------------------------------------------------------------- 1 | #[forbid(unsafe_code)] 2 | #[macro_use] 3 | extern crate log; 4 | 5 | use fast_socks5::{ 6 | client, 7 | server::{transfer, Socks5ServerProtocol}, 8 | util::target_addr::TargetAddr, 9 | ReplyError, Result, Socks5Command, SocksError, 10 | }; 11 | use std::{ 12 | collections::HashSet, 13 | future::Future, 14 | net::{IpAddr, Ipv4Addr, SocketAddr}, 15 | sync::{ 16 | atomic::{AtomicUsize, Ordering}, 17 | Arc, 18 | }, 19 | }; 20 | use structopt::StructOpt; 21 | use tokio::{ 22 | io::{AsyncBufReadExt, AsyncWriteExt}, 23 | net::TcpListener, 24 | sync::RwLock, 25 | task, 26 | }; 27 | 28 | /// # How to use it: 29 | /// 30 | /// Listen on a local address, authentication-free: 31 | /// `$ RUST_LOG=debug cargo run --example router -- --listen-addr 127.0.0.1:1080 no-auth` 32 | /// 33 | /// Listen on a local address, with basic username/password requirement: 34 | /// `$ RUST_LOG=debug cargo run --example router -- --listen-addr 127.0.0.1:1080 password --username admin --password password` 35 | /// 36 | /// Now, connections will be refused since there are no backends. 37 | /// 38 | /// Run a backend proxy, with skipped authentication mode (-k): 39 | /// `$ RUST_LOG=debug cargo run --example server -- --listen-addr 127.0.0.1:1337 --public-addr 127.0.0.1 -k no-auth` 40 | /// 41 | /// Connect to the secret admin console and add the backend: 42 | /// `$ socat --experimental SOCKS5-CONNECT:127.0.0.1:admin.internal:1234 READLINE` 43 | /// `ADD 127.0.0.1:1337` 44 | /// 45 | /// You can add more backends and they'll be used in a round-robin fashion. 46 | /// 47 | #[derive(Debug, StructOpt)] 48 | #[structopt( 49 | name = "socks5-router", 50 | about = "A socks5 demo 'router' proxying requests to further downstream socks5 servers." 51 | )] 52 | struct Opt { 53 | /// Bind on address address. eg. `127.0.0.1:1080` 54 | #[structopt(short, long)] 55 | pub listen_addr: String, 56 | 57 | /// Choose authentication type 58 | #[structopt(subcommand, name = "auth")] // Note that we mark a field as a subcommand 59 | pub auth: AuthMode, 60 | } 61 | 62 | /// Choose the authentication type 63 | #[derive(StructOpt, Debug, PartialEq)] 64 | enum AuthMode { 65 | NoAuth, 66 | Password { 67 | #[structopt(short, long)] 68 | username: String, 69 | 70 | #[structopt(short, long)] 71 | password: String, 72 | }, 73 | } 74 | 75 | #[tokio::main] 76 | async fn main() -> Result<()> { 77 | env_logger::init(); 78 | 79 | spawn_socks_server().await 80 | } 81 | 82 | async fn spawn_socks_server() -> Result<()> { 83 | let opt: &'static Opt = Box::leak(Box::new(Opt::from_args())); 84 | 85 | let backends = Arc::new(RwLock::new(HashSet::new())); 86 | 87 | let listener = TcpListener::bind(&opt.listen_addr).await?; 88 | 89 | info!("Listen for socks connections @ {}", &opt.listen_addr); 90 | 91 | // Standard TCP loop 92 | loop { 93 | match listener.accept().await { 94 | Ok((socket, _client_addr)) => { 95 | spawn_and_log_error(serve_socks5(opt, backends.clone(), socket)); 96 | } 97 | Err(err) => { 98 | error!("accept error = {:?}", err); 99 | } 100 | } 101 | } 102 | } 103 | 104 | static CONN_NUM: AtomicUsize = AtomicUsize::new(0); 105 | 106 | async fn serve_socks5( 107 | opt: &Opt, 108 | backends: Arc>>, 109 | socket: tokio::net::TcpStream, 110 | ) -> Result<(), SocksError> { 111 | let (proto, cmd, target_addr) = match &opt.auth { 112 | AuthMode::NoAuth => Socks5ServerProtocol::accept_no_auth(socket).await?, 113 | AuthMode::Password { username, password } => { 114 | Socks5ServerProtocol::accept_password_auth(socket, |user, pass| { 115 | user == *username && pass == *password 116 | }) 117 | .await? 118 | .0 119 | } 120 | } 121 | .read_command() 122 | .await?; 123 | 124 | if cmd != Socks5Command::TCPConnect { 125 | proto.reply_error(&ReplyError::CommandNotSupported).await?; 126 | return Err(ReplyError::CommandNotSupported.into()); 127 | } 128 | 129 | // Not the most reasonable way to implement an admin interface, 130 | // but rather an example of conditional interception (i.e. just 131 | // not proxying at all and doing something else in-process). 132 | if let TargetAddr::Domain(ref domain, _) = target_addr { 133 | if domain == "admin.internal" { 134 | let inner = proto 135 | .reply_success(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0)) 136 | .await?; 137 | return serve_admin_console(backends, inner).await; 138 | } 139 | } 140 | 141 | let (target_addr, target_port) = target_addr.into_string_and_port(); 142 | 143 | let backends = backends.read().await; 144 | let backends: Vec<_> = backends.iter().collect(); // not good but this is just a demo 145 | if backends.is_empty() { 146 | warn!("No backends! Go add one using the console"); 147 | proto.reply_error(&ReplyError::NetworkUnreachable).await?; 148 | return Ok(()); 149 | } 150 | let n = CONN_NUM.fetch_add(1, Ordering::SeqCst); 151 | 152 | let mut config = client::Config::default(); 153 | config.set_skip_auth(true); 154 | let client = client::Socks5Stream::connect( 155 | backends[n % backends.len()], 156 | target_addr, 157 | target_port, 158 | config, 159 | ) 160 | .await?; 161 | drop(backends); 162 | 163 | let inner = proto 164 | .reply_success(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0)) 165 | .await?; 166 | 167 | transfer(inner, client).await; 168 | Ok(()) 169 | } 170 | 171 | async fn serve_admin_console( 172 | backends: Arc>>, 173 | socket: tokio::net::TcpStream, 174 | ) -> Result<(), SocksError> { 175 | let mut stream = tokio::io::BufReader::new(socket); 176 | stream.write_all(b"Welcome to the router admin console! Use LIST, ADD, or REMOVE commands to manage proxies.\n").await?; 177 | let mut buf = String::with_capacity(128); 178 | while let Ok(_) = stream.read_line(&mut buf).await { 179 | if buf.starts_with("LIST") { 180 | let backends = backends.read().await; 181 | for addr in backends.iter() { 182 | stream.write_all(addr.as_bytes()).await?; 183 | stream.write_all(b"\n").await?; 184 | } 185 | } else if buf.starts_with("ADD ") { 186 | let mut backends = backends.write().await; 187 | if let Some(adr) = buf.strip_prefix("ADD ") { 188 | backends.insert(adr.trim().to_owned()); 189 | } 190 | } else if buf.starts_with("REMOVE ") { 191 | let mut backends = backends.write().await; 192 | if let Some(adr) = buf.strip_prefix("REMOVE ") { 193 | backends.remove(adr.trim()); 194 | } 195 | } 196 | buf.clear(); 197 | } 198 | Ok(()) 199 | } 200 | 201 | fn spawn_and_log_error(fut: F) -> task::JoinHandle<()> 202 | where 203 | F: Future> + Send + 'static, 204 | { 205 | task::spawn(async move { 206 | match fut.await { 207 | Ok(()) => {} 208 | Err(err) => error!("{:#}", &err), 209 | } 210 | }) 211 | } 212 | -------------------------------------------------------------------------------- /examples/server.rs: -------------------------------------------------------------------------------- 1 | #[forbid(unsafe_code)] 2 | #[macro_use] 3 | extern crate log; 4 | 5 | use anyhow::Context; 6 | use fast_socks5::{ 7 | server::{run_tcp_proxy, run_udp_proxy, DnsResolveHelper as _, Socks5ServerProtocol}, 8 | ReplyError, Result, Socks5Command, SocksError, 9 | }; 10 | use std::future::Future; 11 | use structopt::StructOpt; 12 | use tokio::net::TcpListener; 13 | use tokio::task; 14 | 15 | /// # How to use it: 16 | /// 17 | /// Listen on a local address, authentication-free: 18 | /// `$ RUST_LOG=debug cargo run --example server -- --listen-addr 127.0.0.1:1337 no-auth` 19 | /// 20 | /// Listen on a local address, with basic username/password requirement: 21 | /// `$ RUST_LOG=debug cargo run --example server -- --listen-addr 127.0.0.1:1337 password --username admin --password password` 22 | /// 23 | /// Same as above but with UDP support 24 | /// `$ RUST_LOG=debug cargo run --example server -- --listen-addr 127.0.0.1:1337 --allow-udp --public-addr 127.0.0.1 password --username admin --password password` 25 | #[derive(Debug, StructOpt)] 26 | #[structopt( 27 | name = "socks5-server", 28 | about = "A simple implementation of a socks5-server." 29 | )] 30 | struct Opt { 31 | /// Bind on address address. eg. `127.0.0.1:1080` 32 | #[structopt(short, long)] 33 | pub listen_addr: String, 34 | 35 | /// Our external IP address to be sent in reply packets (required for UDP) 36 | #[structopt(long)] 37 | pub public_addr: Option, 38 | 39 | /// Request timeout 40 | #[structopt(short = "t", long, default_value = "10")] 41 | pub request_timeout: u64, 42 | 43 | /// Choose authentication type 44 | #[structopt(subcommand, name = "auth")] // Note that we mark a field as a subcommand 45 | pub auth: AuthMode, 46 | 47 | /// Don't perform the auth handshake, send directly the command request 48 | #[structopt(short = "k", long)] 49 | pub skip_auth: bool, 50 | 51 | /// Allow UDP proxying, requires public-addr to be set 52 | #[structopt(short = "U", long)] 53 | pub allow_udp: bool, 54 | } 55 | 56 | /// Choose the authentication type 57 | #[derive(StructOpt, Debug, PartialEq)] 58 | enum AuthMode { 59 | NoAuth, 60 | Password { 61 | #[structopt(short, long)] 62 | username: String, 63 | 64 | #[structopt(short, long)] 65 | password: String, 66 | }, 67 | } 68 | 69 | /// Useful read 1. https://blog.yoshuawuyts.com/rust-streams/ 70 | /// Useful read 2. https://blog.yoshuawuyts.com/futures-concurrency/ 71 | /// Useful read 3. https://blog.yoshuawuyts.com/streams-concurrency/ 72 | /// error-libs benchmark: https://blog.yoshuawuyts.com/error-handling-survey/ 73 | /// 74 | /// TODO: Write functional tests: https://github.com/ark0f/async-socks5/blob/master/src/lib.rs#L762 75 | /// TODO: Write functional tests with cURL? 76 | #[tokio::main] 77 | async fn main() -> Result<()> { 78 | env_logger::init(); 79 | 80 | spawn_socks_server().await 81 | } 82 | 83 | async fn spawn_socks_server() -> Result<()> { 84 | let opt: &'static Opt = Box::leak(Box::new(Opt::from_args())); 85 | if opt.allow_udp && opt.public_addr.is_none() { 86 | return Err(SocksError::ArgumentInputError( 87 | "Can't allow UDP if public-addr is not set", 88 | )); 89 | } 90 | if opt.skip_auth && opt.auth != AuthMode::NoAuth { 91 | return Err(SocksError::ArgumentInputError( 92 | "Can't use skip-auth flag and authentication altogether.", 93 | )); 94 | } 95 | 96 | let listener = TcpListener::bind(&opt.listen_addr).await?; 97 | 98 | info!("Listen for socks connections @ {}", &opt.listen_addr); 99 | 100 | // Standard TCP loop 101 | loop { 102 | match listener.accept().await { 103 | Ok((socket, _client_addr)) => { 104 | spawn_and_log_error(serve_socks5(opt, socket)); 105 | } 106 | Err(err) => { 107 | error!("accept error = {:?}", err); 108 | } 109 | } 110 | } 111 | } 112 | 113 | async fn serve_socks5(opt: &Opt, socket: tokio::net::TcpStream) -> Result<(), SocksError> { 114 | let (proto, cmd, target_addr) = match &opt.auth { 115 | AuthMode::NoAuth if opt.skip_auth => { 116 | Socks5ServerProtocol::skip_auth_this_is_not_rfc_compliant(socket) 117 | } 118 | AuthMode::NoAuth => Socks5ServerProtocol::accept_no_auth(socket).await?, 119 | AuthMode::Password { username, password } => { 120 | Socks5ServerProtocol::accept_password_auth(socket, |user, pass| { 121 | user == *username && pass == *password 122 | }) 123 | .await? 124 | .0 125 | } 126 | } 127 | .read_command() 128 | .await? 129 | .resolve_dns() 130 | .await?; 131 | 132 | match cmd { 133 | Socks5Command::TCPConnect => { 134 | run_tcp_proxy(proto, &target_addr, opt.request_timeout, false).await?; 135 | } 136 | Socks5Command::UDPAssociate if opt.allow_udp => { 137 | let reply_ip = opt.public_addr.context("invalid reply ip")?; 138 | run_udp_proxy(proto, &target_addr, None, reply_ip, None).await?; 139 | } 140 | _ => { 141 | proto.reply_error(&ReplyError::CommandNotSupported).await?; 142 | return Err(ReplyError::CommandNotSupported.into()); 143 | } 144 | }; 145 | Ok(()) 146 | } 147 | 148 | fn spawn_and_log_error(fut: F) -> task::JoinHandle<()> 149 | where 150 | F: Future> + Send + 'static, 151 | { 152 | task::spawn(async move { 153 | match fut.await { 154 | Ok(()) => {} 155 | Err(err) => error!("{:#}", &err), 156 | } 157 | }) 158 | } 159 | -------------------------------------------------------------------------------- /examples/udp_client.rs: -------------------------------------------------------------------------------- 1 | #[forbid(unsafe_code)] 2 | #[macro_use] 3 | extern crate log; 4 | 5 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; 6 | 7 | use fast_socks5::{client::Socks5Datagram, Result}; 8 | use structopt::StructOpt; 9 | use tokio::{ 10 | io::{AsyncRead, AsyncWrite}, 11 | net::TcpStream, 12 | }; 13 | 14 | /// # How to use it: 15 | /// 16 | /// Query by IPv4 address: 17 | /// `$ RUST_LOG=debug cargo run --example udp_client -- --socks-server 127.0.0.1:1337 --username admin --password password -a 8.8.8.8 -d github.com` 18 | /// 19 | /// Query by IPv6 address: 20 | /// `$ RUST_LOG=debug cargo run --example udp_client -- --socks-server 127.0.0.1:1337 --username admin --password password -a 2001:4860:4860::8888 -d github.com` 21 | /// 22 | /// Query by domain name: 23 | /// `$ RUST_LOG=debug cargo run --example udp_client -- --socks-server 127.0.0.1:1337 --username admin --password password -a dns.google -d github.com` 24 | /// 25 | #[derive(Debug, StructOpt)] 26 | #[structopt( 27 | name = "socks5-udp-client", 28 | about = "A simple example of a socks5 UDP client (proxied DNS client)." 29 | )] 30 | struct Opt { 31 | /// Socks5 server address + port, e.g. `127.0.0.1:1080` 32 | #[structopt(short, long)] 33 | pub socks_server: SocketAddr, 34 | 35 | /// Target (DNS) server address, e.g. `8.8.8.8` 36 | #[structopt(short = "a", long)] 37 | pub target_server: String, 38 | 39 | /// Target (DNS) server port, by default 53 40 | #[structopt(short = "p", long)] 41 | pub target_port: Option, 42 | 43 | #[structopt(short = "d", long)] 44 | pub query_domain: String, 45 | 46 | #[structopt(short, long)] 47 | pub username: Option, 48 | 49 | #[structopt(long)] 50 | pub password: Option, 51 | } 52 | 53 | #[tokio::main] 54 | async fn main() -> Result<()> { 55 | env_logger::init(); 56 | 57 | spawn_socks_client().await 58 | } 59 | 60 | async fn spawn_socks_client() -> Result<()> { 61 | let opt: Opt = Opt::from_args(); 62 | 63 | // Creating a SOCKS stream to the target address through the socks server 64 | let backing_socket = TcpStream::connect(opt.socks_server).await?; 65 | // At least on some platforms it is important to use the same protocol as the server 66 | // XXX: assumes the returned UDP proxy will have the same protocol as the socks_server 67 | let client_bind_addr = if opt.socks_server.is_ipv4() { 68 | SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0) 69 | } else { 70 | SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0) 71 | }; 72 | let mut socks = match opt.username { 73 | Some(username) => { 74 | Socks5Datagram::bind_with_password( 75 | backing_socket, 76 | client_bind_addr, 77 | &username, 78 | &opt.password.expect("Please fill the password"), 79 | ) 80 | .await? 81 | } 82 | 83 | _ => Socks5Datagram::bind(backing_socket, client_bind_addr).await?, 84 | }; 85 | 86 | // Once socket creation is completed, can start to communicate with the server 87 | dns_request( 88 | &mut socks, 89 | opt.target_server, 90 | opt.target_port.unwrap_or(53), 91 | opt.query_domain, 92 | ) 93 | .await?; 94 | 95 | Ok(()) 96 | } 97 | 98 | /// Simple DNS request 99 | async fn dns_request( 100 | socket: &mut Socks5Datagram, 101 | server: String, 102 | port: u16, 103 | domain: String, 104 | ) -> Result<()> { 105 | debug!("Requesting results..."); 106 | 107 | let mut query: Vec = vec![ 108 | 0x13, 0x37, // txid 109 | 0x01, 0x00, // flags 110 | 0x00, 0x01, // questions 111 | 0x00, 0x00, // answer RRs 112 | 0x00, 0x00, // authority RRs 113 | 0x00, 0x00, // additional RRs 114 | ]; 115 | for part in domain.split('.') { 116 | query.push(part.len() as u8); 117 | query.extend(part.chars().map(|c| c as u8)); 118 | } 119 | query.extend_from_slice(&[0, 0, 1, 0, 1]); 120 | debug!("query: {:?}", query); 121 | 122 | let _sent = socket.send_to(&query, (&server[..], port)).await?; 123 | 124 | let mut buf = [0u8; 256]; 125 | let (len, adr) = socket.recv_from(&mut buf).await?; 126 | let msg = &buf[..len]; 127 | info!("response: {:?} from {:?}", msg, adr); 128 | 129 | assert_eq!(msg[0], 0x13); 130 | assert_eq!(msg[1], 0x37); 131 | 132 | Ok(()) 133 | } 134 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use crate::read_exact; 2 | use crate::util::stream::{tcp_connect, tcp_connect_with_timeout}; 3 | use crate::util::target_addr::{read_address, TargetAddr, ToTargetAddr}; 4 | use crate::{ 5 | consts, new_udp_header, parse_udp_request, AuthenticationMethod, ReplyError, Result, 6 | Socks5Command, SocksError, 7 | }; 8 | use anyhow::Context; 9 | use std::io; 10 | use std::net::SocketAddr; 11 | use std::net::ToSocketAddrs; 12 | use std::pin::Pin; 13 | use std::task::Poll; 14 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 15 | use tokio::net::{TcpStream, UdpSocket}; 16 | 17 | const MAX_ADDR_LEN: usize = 260; 18 | 19 | #[derive(Debug)] 20 | pub struct Config { 21 | /// Timeout of the socket connect 22 | connect_timeout: Option, 23 | /// Avoid useless roundtrips if we don't need the Authentication layer 24 | /// make sure to also activate it on the server side. 25 | skip_auth: bool, 26 | } 27 | 28 | impl Default for Config { 29 | fn default() -> Self { 30 | Config { 31 | connect_timeout: None, 32 | skip_auth: false, 33 | } 34 | } 35 | } 36 | 37 | impl Config { 38 | /// How much time it should wait until the socket connect times out. 39 | pub fn set_connect_timeout(&mut self, n: u64) -> &mut Self { 40 | self.connect_timeout = Some(n); 41 | self 42 | } 43 | 44 | pub fn set_skip_auth(&mut self, value: bool) -> &mut Self { 45 | self.skip_auth = value; 46 | self 47 | } 48 | } 49 | 50 | /// A SOCKS5 client. 51 | /// `Socks5Stream` implements [`AsyncRead`] and [`AsyncWrite`]. 52 | #[derive(Debug)] 53 | pub struct Socks5Stream { 54 | socket: S, 55 | target_addr: Option, 56 | config: Config, 57 | } 58 | 59 | impl Socks5Stream 60 | where 61 | S: AsyncRead + AsyncWrite + Unpin, 62 | { 63 | /// Possibility to use a stream already created rather than 64 | /// creating a whole new `TcpStream::connect()`. 65 | pub async fn use_stream( 66 | socket: S, 67 | auth: Option, 68 | config: Config, 69 | ) -> Result { 70 | let mut stream = Socks5Stream { 71 | socket, 72 | config, 73 | target_addr: None, 74 | }; 75 | 76 | // Auth none is always used by default. 77 | let mut methods = vec![AuthenticationMethod::None]; 78 | 79 | if let Some(method) = auth { 80 | // add any other method if supplied 81 | methods.push(method); 82 | } 83 | 84 | // Handshake Lifecycle 85 | if !stream.config.skip_auth { 86 | let methods = stream.send_version_and_methods(methods).await?; 87 | stream.which_method_accepted(methods).await?; 88 | } else { 89 | debug!("skipping auth"); 90 | } 91 | 92 | Ok(stream) 93 | } 94 | 95 | pub async fn request( 96 | &mut self, 97 | cmd: Socks5Command, 98 | target_addr: TargetAddr, 99 | ) -> Result { 100 | self.target_addr = Some(target_addr); 101 | 102 | // Request Lifecycle 103 | info!("Requesting headers `{:?}`...", &self.target_addr); 104 | self.request_header(cmd).await?; 105 | let bind_addr = self.read_request_reply().await?; 106 | 107 | Ok(bind_addr) 108 | } 109 | 110 | /// Decide to whether or not, accept the authentication method 111 | /// A client send a list of methods that he supports, he could send 112 | /// 113 | /// - 0: Non auth 114 | /// - 2: Auth with username/password 115 | /// 116 | /// Altogether, then the server choose to use of of these, 117 | /// or deny the handshake (thus the connection). 118 | /// 119 | /// # Examples 120 | /// ```text 121 | /// {SOCKS Version, methods-length} 122 | /// eg. (non-auth) {5, 2} 123 | /// eg. (auth) {5, 3} 124 | /// ``` 125 | /// 126 | async fn send_version_and_methods( 127 | &mut self, 128 | methods: Vec, 129 | ) -> Result> { 130 | debug!( 131 | "Client's version and method len [{}, {}]", 132 | consts::SOCKS5_VERSION, 133 | methods.len() 134 | ); 135 | // the first 2 bytes which contains the SOCKS version and the methods len() 136 | let mut packet = vec![consts::SOCKS5_VERSION, methods.len() as u8]; 137 | 138 | let auth = methods.iter().map(|l| l.as_u8()).collect::>(); 139 | debug!("client auth methods supported: {:?}", &auth); 140 | packet.extend(auth); 141 | 142 | self.socket 143 | .write_all(&packet) 144 | .await 145 | .context("Couldn't write SOCKS version & methods len & supported auth methods")?; 146 | 147 | // Return methods available 148 | Ok(methods) 149 | } 150 | 151 | /// Decide to whether or not, accept the authentication method. 152 | /// Don't forget that the methods list sent by the client, contains one or more methods. 153 | /// 154 | /// # Request 155 | /// 156 | /// Client send an array of 3 entries: [0, 1, 2] 157 | /// ```text 158 | /// {SOCKS Version, Authentication chosen} 159 | /// eg. (non-auth) {5, 0} 160 | /// eg. (GSSAPI) {5, 1} 161 | /// eg. (auth) {5, 2} 162 | /// ``` 163 | /// 164 | /// # Response 165 | /// ```text 166 | /// eg. (accept non-auth) {5, 0x00} 167 | /// eg. (non-acceptable) {5, 0xff} 168 | /// ``` 169 | /// 170 | async fn which_method_accepted(&mut self, methods: Vec) -> Result<()> { 171 | let [version, method] = 172 | read_exact!(self.socket, [0u8; 2]).context("Can't get chosen auth method")?; 173 | debug!( 174 | "Socks version ({version}), method chosen: {method}.", 175 | version = version, 176 | method = method, 177 | ); 178 | 179 | if version != consts::SOCKS5_VERSION { 180 | return Err(SocksError::UnsupportedSocksVersion(version)); 181 | } 182 | 183 | match method { 184 | consts::SOCKS5_AUTH_METHOD_NONE => info!("No auth will be used"), 185 | consts::SOCKS5_AUTH_METHOD_PASSWORD => self.use_password_auth(methods).await?, 186 | _ => { 187 | debug!("Don't support this auth method, reply with (0xff)"); 188 | self.socket 189 | .write_all(&[ 190 | consts::SOCKS5_VERSION, 191 | consts::SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE, 192 | ]) 193 | .await 194 | .context("Can't write that the methods are unsupported.")?; 195 | 196 | return Err(SocksError::AuthMethodUnacceptable(vec![method])); 197 | } 198 | } 199 | 200 | Ok(()) 201 | } 202 | 203 | async fn use_password_auth(&mut self, methods: Vec) -> Result<()> { 204 | info!("Password will be used"); 205 | let (username, password) = match methods.get(1) { 206 | Some(AuthenticationMethod::None) => unreachable!(), 207 | Some(AuthenticationMethod::Password { 208 | ref username, 209 | ref password, 210 | }) => Ok((username, password)), 211 | None => Err(SocksError::AuthenticationRejected(format!( 212 | "Authentication rejected, missing user pass" 213 | ))), 214 | }?; 215 | 216 | let user_bytes = username.as_bytes(); 217 | let pass_bytes = password.as_bytes(); 218 | 219 | let mut packet: Vec = vec![1, user_bytes.len() as u8]; 220 | packet.extend(user_bytes); 221 | packet.push(pass_bytes.len() as u8); 222 | packet.extend(pass_bytes); 223 | 224 | self.socket 225 | .write_all(&packet) 226 | .await 227 | .context("Can't send password")?; 228 | 229 | // Check the server reply, if whether it approved the auth or not 230 | let [version, is_success] = 231 | read_exact!(self.socket, [0u8; 2]).context("Can't read is_success")?; 232 | debug!( 233 | "Auth: [version: {version}, is_success: {is_success}]", 234 | version = version, 235 | is_success = is_success, 236 | ); 237 | 238 | if is_success != consts::SOCKS5_REPLY_SUCCEEDED { 239 | return Err(SocksError::AuthenticationRejected(format!( 240 | "Authentication with username `{}`, rejected.", 241 | username 242 | ))); 243 | } 244 | 245 | Ok(()) 246 | } 247 | 248 | /// Decide to whether or not, accept the authentication method. 249 | /// Don't forget that the methods list sent by the client, contains one or more methods. 250 | /// 251 | /// # Request 252 | /// ```test 253 | /// +----+-----+-------+------+----------+----------+ 254 | /// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 255 | /// +----+-----+-------+------+----------+----------+ 256 | /// | 1 | 1 | 1 | 1 | Variable | 2 | 257 | /// +----+-----+-------+------+----------+----------+ 258 | /// ``` 259 | /// 260 | /// # Help 261 | /// 262 | /// To debug request use a netcat server with hexadecimal output to parse the hidden bytes: 263 | /// 264 | /// ```bash 265 | /// $ nc -k -l 80 | hexdump -C 266 | /// ``` 267 | /// 268 | async fn request_header(&mut self, cmd: Socks5Command) -> Result<()> { 269 | let mut packet = [0u8; MAX_ADDR_LEN + 3]; 270 | let padding; // maximum len of the headers sent 271 | // build our request packet with (socks version, Command, reserved) 272 | packet[..3].copy_from_slice(&[consts::SOCKS5_VERSION, cmd.as_u8(), 0x00]); 273 | 274 | match self.target_addr.as_ref() { 275 | None => { 276 | if cmd == Socks5Command::UDPAssociate { 277 | debug!("UDPAssociate without target_addr, fallback to zeros."); 278 | padding = 10; 279 | 280 | packet[3] = 0x01; 281 | packet[4..8].copy_from_slice(&[0, 0, 0, 0]); // ip 282 | packet[8..padding].copy_from_slice(&[0, 0]); // port 283 | } else { 284 | return Err(anyhow::Error::msg("target addr should be present").into()); 285 | } 286 | } 287 | Some(target_addr) => match target_addr { 288 | TargetAddr::Ip(SocketAddr::V4(addr)) => { 289 | debug!("TargetAddr::IpV4"); 290 | padding = 10; 291 | 292 | packet[3] = 0x01; 293 | debug!("addr ip {:?}", (*addr.ip()).octets()); 294 | packet[4..8].copy_from_slice(&(addr.ip()).octets()); // ip 295 | packet[8..padding].copy_from_slice(&addr.port().to_be_bytes()); 296 | // port 297 | } 298 | TargetAddr::Ip(SocketAddr::V6(addr)) => { 299 | debug!("TargetAddr::IpV6"); 300 | padding = 22; 301 | 302 | packet[3] = 0x04; 303 | debug!("addr ip {:?}", (*addr.ip()).octets()); 304 | packet[4..20].copy_from_slice(&(addr.ip()).octets()); // ip 305 | packet[20..padding].copy_from_slice(&addr.port().to_be_bytes()); 306 | // port 307 | } 308 | TargetAddr::Domain(ref domain, port) => { 309 | debug!("TargetAddr::Domain"); 310 | if domain.len() > u8::MAX as usize { 311 | return Err(SocksError::ExceededMaxDomainLen(domain.len())); 312 | } 313 | padding = 5 + domain.len() + 2; 314 | 315 | packet[3] = 0x03; // Specify domain type 316 | packet[4] = domain.len() as u8; // domain length 317 | packet[5..(5 + domain.len())].copy_from_slice(domain.as_bytes()); // domain content 318 | packet[(5 + domain.len())..padding].copy_from_slice(&port.to_be_bytes()); 319 | // port content (.to_be_bytes() convert from u16 to u8 type) 320 | } 321 | }, 322 | } 323 | 324 | debug!("Bytes long version: {:?}", &packet[..]); 325 | debug!("Bytes shorted version: {:?}", &packet[..padding]); 326 | debug!("Padding: {}", &padding); 327 | 328 | // we limit the end of the packet right after the domain + port number, we don't need to print 329 | // useless 0 bytes, otherwise other protocol won't understand the request (like HTTP servers). 330 | self.socket 331 | .write(&packet[..padding]) 332 | .await 333 | .context("Can't write request header's packet.")?; 334 | 335 | self.socket 336 | .flush() 337 | .await 338 | .context("Can't flush request header's packet")?; 339 | 340 | Ok(()) 341 | } 342 | 343 | /// The server send a confirmation (reply) that he had successfully connected (or not) to the 344 | /// remote server. 345 | async fn read_request_reply(&mut self) -> Result { 346 | let [version, reply, rsv, address_type] = 347 | read_exact!(self.socket, [0u8; 4]).context("Received malformed reply")?; 348 | 349 | debug!( 350 | "Reply received: [version: {version}, reply: {reply}, rsv: {rsv}, address_type: {address_type}]", 351 | version = version, 352 | reply = reply, 353 | rsv = rsv, 354 | address_type = address_type, 355 | ); 356 | 357 | if version != consts::SOCKS5_VERSION { 358 | return Err(SocksError::UnsupportedSocksVersion(version)); 359 | } 360 | 361 | if reply != consts::SOCKS5_REPLY_SUCCEEDED { 362 | return Err(ReplyError::from_u8(reply).into()); // Convert reply received into correct error 363 | } 364 | 365 | let address = read_address(&mut self.socket, address_type).await?; 366 | info!("Remote server bind on {}.", address); 367 | 368 | Ok(address) 369 | } 370 | 371 | pub fn get_socket(self) -> S { 372 | self.socket 373 | } 374 | 375 | pub fn get_socket_ref(&self) -> &S { 376 | &self.socket 377 | } 378 | 379 | pub fn get_socket_mut(&mut self) -> &mut S { 380 | &mut self.socket 381 | } 382 | } 383 | 384 | /// A SOCKS5 UDP client. 385 | #[derive(Debug)] 386 | pub struct Socks5Datagram { 387 | socket: UdpSocket, 388 | // keeps the session alive 389 | #[allow(dead_code)] 390 | stream: Socks5Stream, 391 | proxy_addr: Option, 392 | } 393 | 394 | impl Socks5Datagram { 395 | /// Creates a UDP socket bound to the specified address which will have its 396 | /// traffic routed through the specified proxy. 397 | /// 398 | /// # Arguments 399 | /// * `backing_socket` - The underlying socket carrying the socks5 traffic. 400 | /// * `client_bind_addr` - A socket address indicates the binding source address used to 401 | /// communicate with the socks5 server. 402 | /// 403 | /// # Examples 404 | /// ```no_run 405 | /// # use tokio::net::TcpStream; 406 | /// # use fast_socks5::client; 407 | /// # #[tokio::main] 408 | /// # async fn main() -> Result<(), Box>{ 409 | /// let backing_socket = TcpStream::connect("127.0.0.1:1080").await?; 410 | /// let tunnel = client::Socks5Datagram::bind(backing_socket, "[::]:0").await?; 411 | /// # Ok(()) 412 | /// # } 413 | /// ``` 414 | pub async fn bind(backing_socket: S, client_bind_addr: U) -> Result> 415 | where 416 | U: ToSocketAddrs, 417 | { 418 | Self::bind_internal(backing_socket, Self::create_out_sock(client_bind_addr).await?, None).await 419 | } 420 | /// Creates a UDP socket bound to the specified address which will have its 421 | /// traffic routed through the specified proxy. The given username and password 422 | /// is used to authenticate to the SOCKS proxy. 423 | pub async fn bind_with_password( 424 | backing_socket: S, 425 | client_bind_addr: U, 426 | username: &str, 427 | password: &str, 428 | ) -> Result> 429 | where 430 | U: ToSocketAddrs, 431 | { 432 | let auth = AuthenticationMethod::Password { 433 | username: username.to_owned(), 434 | password: password.to_owned(), 435 | }; 436 | Self::bind_internal(backing_socket, Self::create_out_sock(client_bind_addr).await?, Some(auth)).await 437 | } 438 | /// Use a UdpSocket already created rather than creating a whole new `UdpSocket::bind`. 439 | pub async fn use_socket( 440 | backing_socket: S, 441 | out_sock: UdpSocket, 442 | ) -> Result> { 443 | Self::bind_internal(backing_socket, out_sock, None).await 444 | } 445 | /// Same as `use_socket` but with credentials. 446 | pub async fn use_socket_with_password( 447 | backing_socket: S, 448 | out_sock: UdpSocket, 449 | username: &str, 450 | password: &str, 451 | ) -> Result> { 452 | let auth = AuthenticationMethod::Password { 453 | username: username.to_owned(), 454 | password: password.to_owned(), 455 | }; 456 | Self::bind_internal(backing_socket, out_sock, Some(auth)).await 457 | } 458 | 459 | async fn create_out_sock(client_bind_addr: U) -> Result { 460 | let client_bind_addr = client_bind_addr 461 | .to_socket_addrs()? 462 | .next() 463 | .context("unreachable")?; 464 | let out_sock = UdpSocket::bind(client_bind_addr).await?; 465 | info!("UdpSocket client socket bind to {}", client_bind_addr); 466 | Ok(out_sock) 467 | } 468 | 469 | async fn bind_internal( 470 | backing_socket: S, 471 | out_sock: UdpSocket, 472 | auth: Option, 473 | ) -> Result> 474 | { 475 | // Init socks5 stream. 476 | let mut proxy_stream = 477 | Socks5Stream::use_stream(backing_socket, auth, Config::default()).await?; 478 | 479 | // we don't know what our IP is from the perspective of the proxy, so 480 | // don't try to pass `addr` in here. 481 | let client_src = TargetAddr::Ip("[::]:0".parse().unwrap()); 482 | let proxy_addr = proxy_stream 483 | .request(Socks5Command::UDPAssociate, client_src) 484 | .await?; 485 | 486 | let proxy_addr_resolved = proxy_addr 487 | .to_socket_addrs()? 488 | .next() 489 | .context("unreachable")?; 490 | info!("UdpSocket client connecting to {}", proxy_addr_resolved); 491 | out_sock.connect(proxy_addr_resolved).await?; 492 | info!("UdpSocket client connected"); 493 | 494 | Ok(Socks5Datagram { 495 | socket: out_sock, 496 | stream: proxy_stream, 497 | proxy_addr: Some(proxy_addr), 498 | }) 499 | } 500 | 501 | /// Like `UdpSocket::send_to`. 502 | /// 503 | /// # Note 504 | /// 505 | /// The SOCKS protocol inserts a header at the beginning of the message. The 506 | /// header will be 10 bytes for an IPv4 address, 22 bytes for an IPv6 507 | /// address, and 7 bytes plus the length of the domain for a domain address. 508 | pub async fn send_to(&self, data: &[u8], addr: A) -> Result 509 | where 510 | A: ToTargetAddr, 511 | { 512 | let mut buf = new_udp_header(addr)?; 513 | let buf_len = buf.len(); 514 | buf.extend_from_slice(data); 515 | 516 | return Ok(self.socket.send(&buf).await? - buf_len); 517 | } 518 | 519 | /// Like `UdpSocket::recv_from`. 520 | pub async fn recv_from(&self, data_store: &mut [u8]) -> Result<(usize, TargetAddr)> { 521 | let mut buf = [0u8; 0x10000]; 522 | let (size, _) = self.socket.recv_from(&mut buf).await?; 523 | 524 | let (frag, target_addr, data) = parse_udp_request(&mut buf[..size]).await?; 525 | 526 | if frag != 0 { 527 | return Err(SocksError::Other(anyhow::anyhow!( 528 | "Unsupported frag value." 529 | ))); 530 | } 531 | 532 | data_store[..data.len()].copy_from_slice(data); 533 | Ok((data.len(), target_addr)) 534 | } 535 | 536 | /// Returns the address of the proxy-side UDP socket through which all 537 | /// messages will be routed. 538 | pub fn proxy_addr(&self) -> Result<&TargetAddr> { 539 | Ok(self 540 | .proxy_addr 541 | .as_ref() 542 | .context("proxy addr is not ready")?) 543 | } 544 | 545 | /// Returns a shared reference to the inner socket. 546 | pub fn get_ref(&self) -> &UdpSocket { 547 | &self.socket 548 | } 549 | 550 | /// Returns a mutable reference to the inner socket. 551 | pub fn get_mut(&mut self) -> &mut UdpSocket { 552 | &mut self.socket 553 | } 554 | } 555 | 556 | /// Api if you want to use TcpStream to create a new connection to the SOCKS5 server. 557 | impl Socks5Stream { 558 | /// Connects to a target server through a SOCKS5 proxy. 559 | pub async fn connect( 560 | socks_server: T, 561 | target_addr: String, 562 | target_port: u16, 563 | config: Config, 564 | ) -> Result 565 | where 566 | T: ToSocketAddrs, 567 | { 568 | Self::connect_raw( 569 | Socks5Command::TCPConnect, 570 | socks_server, 571 | target_addr, 572 | target_port, 573 | None, 574 | config, 575 | ) 576 | .await 577 | } 578 | 579 | /// Connect with credentials 580 | pub async fn connect_with_password( 581 | socks_server: T, 582 | target_addr: String, 583 | target_port: u16, 584 | username: String, 585 | password: String, 586 | config: Config, 587 | ) -> Result 588 | where 589 | T: ToSocketAddrs, 590 | { 591 | let auth = AuthenticationMethod::Password { username, password }; 592 | 593 | Self::connect_raw( 594 | Socks5Command::TCPConnect, 595 | socks_server, 596 | target_addr, 597 | target_port, 598 | Some(auth), 599 | config, 600 | ) 601 | .await 602 | } 603 | 604 | /// Process clients SOCKS requests 605 | /// This is the entry point where a whole request is processed. 606 | pub async fn connect_raw( 607 | cmd: Socks5Command, 608 | socks_server: T, 609 | target_addr: String, 610 | target_port: u16, 611 | auth: Option, 612 | config: Config, 613 | ) -> Result 614 | where 615 | T: ToSocketAddrs, 616 | { 617 | let addr = socks_server 618 | .to_socket_addrs()? 619 | .next() 620 | .context("unreachable")?; 621 | let socket = match config.connect_timeout { 622 | None => tcp_connect(addr).await?, 623 | Some(connect_timeout) => tcp_connect_with_timeout(addr, connect_timeout).await?, 624 | }; 625 | info!("Connected @ {}", &socket.peer_addr()?); 626 | 627 | // Specify the target, here domain name, dns will be resolved on the server side 628 | let target_addr = (target_addr.as_str(), target_port) 629 | .to_target_addr() 630 | .context("Can't convert address to TargetAddr format")?; 631 | 632 | // upgrade the TcpStream to Socks5Stream 633 | let mut socks_stream = Self::use_stream(socket, auth, config).await?; 634 | socks_stream.request(cmd, target_addr).await?; 635 | 636 | Ok(socks_stream) 637 | } 638 | } 639 | 640 | /// Allow us to read directly from the struct 641 | impl AsyncRead for Socks5Stream 642 | where 643 | S: AsyncRead + AsyncWrite + Unpin, 644 | { 645 | fn poll_read( 646 | mut self: Pin<&mut Self>, 647 | context: &mut std::task::Context, 648 | buf: &mut tokio::io::ReadBuf<'_>, 649 | ) -> Poll> { 650 | Pin::new(&mut self.socket).poll_read(context, buf) 651 | } 652 | } 653 | 654 | /// Allow us to write directly into the struct 655 | impl AsyncWrite for Socks5Stream 656 | where 657 | S: AsyncRead + AsyncWrite + Unpin, 658 | { 659 | fn poll_write( 660 | mut self: Pin<&mut Self>, 661 | context: &mut std::task::Context, 662 | buf: &[u8], 663 | ) -> Poll> { 664 | Pin::new(&mut self.socket).poll_write(context, buf) 665 | } 666 | 667 | fn poll_flush( 668 | mut self: Pin<&mut Self>, 669 | context: &mut std::task::Context, 670 | ) -> Poll> { 671 | Pin::new(&mut self.socket).poll_flush(context) 672 | } 673 | 674 | fn poll_shutdown( 675 | mut self: Pin<&mut Self>, 676 | context: &mut std::task::Context, 677 | ) -> Poll> { 678 | Pin::new(&mut self.socket).poll_shutdown(context) 679 | } 680 | } 681 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Fast SOCKS5 client/server implementation written in Rust async/.await (with tokio). 2 | //! 3 | //! This library is maintained by [anyip.io](https://anyip.io/) a residential and mobile socks5 proxy provider. 4 | //! 5 | //! ## Features 6 | //! 7 | //! - An `async`/`.await` [SOCKS5](https://tools.ietf.org/html/rfc1928) implementation. 8 | //! - An `async`/`.await` [SOCKS4 Client](https://www.openssh.com/txt/socks4.protocol) implementation. 9 | //! - An `async`/`.await` [SOCKS4a Client](https://www.openssh.com/txt/socks4a.protocol) implementation. 10 | //! - No **unsafe** code 11 | //! - Built on top of the [Tokio](https://tokio.rs/) runtime 12 | //! - Ultra lightweight and scalable 13 | //! - No system dependencies 14 | //! - Cross-platform 15 | //! - Infinitely extensible, explicit server API based on typestates for safety 16 | //! - You control the request handling, the library only ensures you follow the proper protocol flow 17 | //! - Can skip DNS resolution 18 | //! - Can skip the authentication/handshake process (not RFC-compliant, for private use, to save on useless round-trips) 19 | //! - Instead of proxying in-process, swap out `run_tcp_proxy` for custom handling to build a router or to use a custom accelerated proxying method 20 | //! - Authentication methods: 21 | //! - No-Auth method (`0x00`) 22 | //! - Username/Password auth method (`0x02`) 23 | //! - Custom auth methods can be implemented on the server side via the `AuthMethod` Trait 24 | //! - Multiple auth methods with runtime negotiation can be supported, with fast *static* dispatch (enums can be generated with the `auth_method_enums` macro) 25 | //! - UDP is supported 26 | //! - All SOCKS5 RFC errors (replies) should be mapped 27 | //! - `IPv4`, `IPv6`, and `Domains` types are supported 28 | //! 29 | //! ## Install 30 | //! 31 | //! Open in [crates.io](https://crates.io/crates/fast-socks5). 32 | //! 33 | //! 34 | //! ## Examples 35 | //! 36 | //! Please check [`examples`](https://github.com/dizda/fast-socks5/tree/master/examples) directory. 37 | 38 | #![forbid(unsafe_code)] 39 | #[macro_use] 40 | extern crate log; 41 | 42 | pub mod client; 43 | pub mod server; 44 | pub mod util; 45 | 46 | #[cfg(feature = "socks4")] 47 | pub mod socks4; 48 | 49 | use std::fmt; 50 | use std::io; 51 | use thiserror::Error; 52 | use util::stream::ConnectError; 53 | use util::target_addr::read_address; 54 | use util::target_addr::AddrError; 55 | use util::target_addr::TargetAddr; 56 | use util::target_addr::ToTargetAddr; 57 | 58 | use tokio::io::AsyncReadExt; 59 | 60 | #[rustfmt::skip] 61 | pub mod consts { 62 | pub const SOCKS5_VERSION: u8 = 0x05; 63 | 64 | pub const SOCKS5_AUTH_METHOD_NONE: u8 = 0x00; 65 | pub const SOCKS5_AUTH_METHOD_GSSAPI: u8 = 0x01; 66 | pub const SOCKS5_AUTH_METHOD_PASSWORD: u8 = 0x02; 67 | pub const SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE: u8 = 0xff; 68 | 69 | pub const SOCKS5_CMD_TCP_CONNECT: u8 = 0x01; 70 | pub const SOCKS5_CMD_TCP_BIND: u8 = 0x02; 71 | pub const SOCKS5_CMD_UDP_ASSOCIATE: u8 = 0x03; 72 | 73 | pub const SOCKS5_ADDR_TYPE_IPV4: u8 = 0x01; 74 | pub const SOCKS5_ADDR_TYPE_DOMAIN_NAME: u8 = 0x03; 75 | pub const SOCKS5_ADDR_TYPE_IPV6: u8 = 0x04; 76 | 77 | pub const SOCKS5_REPLY_SUCCEEDED: u8 = 0x00; 78 | pub const SOCKS5_REPLY_GENERAL_FAILURE: u8 = 0x01; 79 | pub const SOCKS5_REPLY_CONNECTION_NOT_ALLOWED: u8 = 0x02; 80 | pub const SOCKS5_REPLY_NETWORK_UNREACHABLE: u8 = 0x03; 81 | pub const SOCKS5_REPLY_HOST_UNREACHABLE: u8 = 0x04; 82 | pub const SOCKS5_REPLY_CONNECTION_REFUSED: u8 = 0x05; 83 | pub const SOCKS5_REPLY_TTL_EXPIRED: u8 = 0x06; 84 | pub const SOCKS5_REPLY_COMMAND_NOT_SUPPORTED: u8 = 0x07; 85 | pub const SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED: u8 = 0x08; 86 | } 87 | 88 | #[derive(Debug, PartialEq)] 89 | pub enum Socks5Command { 90 | TCPConnect, 91 | TCPBind, 92 | UDPAssociate, 93 | } 94 | 95 | #[allow(dead_code)] 96 | impl Socks5Command { 97 | #[inline] 98 | #[rustfmt::skip] 99 | fn as_u8(&self) -> u8 { 100 | match self { 101 | Socks5Command::TCPConnect => consts::SOCKS5_CMD_TCP_CONNECT, 102 | Socks5Command::TCPBind => consts::SOCKS5_CMD_TCP_BIND, 103 | Socks5Command::UDPAssociate => consts::SOCKS5_CMD_UDP_ASSOCIATE, 104 | } 105 | } 106 | 107 | #[inline] 108 | #[rustfmt::skip] 109 | fn from_u8(code: u8) -> Option { 110 | match code { 111 | consts::SOCKS5_CMD_TCP_CONNECT => Some(Socks5Command::TCPConnect), 112 | consts::SOCKS5_CMD_TCP_BIND => Some(Socks5Command::TCPBind), 113 | consts::SOCKS5_CMD_UDP_ASSOCIATE => Some(Socks5Command::UDPAssociate), 114 | _ => None, 115 | } 116 | } 117 | } 118 | 119 | #[derive(Debug, PartialEq)] 120 | pub enum AuthenticationMethod { 121 | None, 122 | Password { username: String, password: String }, 123 | } 124 | 125 | impl AuthenticationMethod { 126 | #[inline] 127 | #[rustfmt::skip] 128 | fn as_u8(&self) -> u8 { 129 | match self { 130 | AuthenticationMethod::None => consts::SOCKS5_AUTH_METHOD_NONE, 131 | AuthenticationMethod::Password {..} => 132 | consts::SOCKS5_AUTH_METHOD_PASSWORD 133 | } 134 | } 135 | 136 | #[inline] 137 | #[rustfmt::skip] 138 | fn from_u8(code: u8) -> Option { 139 | match code { 140 | consts::SOCKS5_AUTH_METHOD_NONE => Some(AuthenticationMethod::None), 141 | consts::SOCKS5_AUTH_METHOD_PASSWORD => Some(AuthenticationMethod::Password { username: "test".to_string(), password: "test".to_string()}), 142 | _ => None, 143 | } 144 | } 145 | } 146 | 147 | impl fmt::Display for AuthenticationMethod { 148 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 149 | match *self { 150 | AuthenticationMethod::None => f.write_str("AuthenticationMethod::None"), 151 | AuthenticationMethod::Password { .. } => f.write_str("AuthenticationMethod::Password"), 152 | } 153 | } 154 | } 155 | 156 | //impl Vec { 157 | // pub fn as_bytes(&self) -> &[u8] { 158 | // self.iter().map(|l| l.as_u8()).collect() 159 | // } 160 | //} 161 | // 162 | //impl From<&[AuthenticationMethod]> for &[u8] { 163 | // fn from(_: Vec) -> Self { 164 | // &[0x00] 165 | // } 166 | //} 167 | 168 | #[derive(Error, Debug)] 169 | pub enum SocksError { 170 | #[error("i/o error: {0}")] 171 | Io(#[from] io::Error), 172 | #[error("the data for key `{0}` is not available")] 173 | Redaction(String), 174 | #[error("invalid header (expected {expected:?}, found {found:?})")] 175 | InvalidHeader { expected: String, found: String }, 176 | 177 | #[error("Auth method unacceptable `{0:?}`.")] 178 | AuthMethodUnacceptable(Vec), 179 | #[error("Unsupported SOCKS version `{0}`.")] 180 | UnsupportedSocksVersion(u8), 181 | #[error("Domain exceeded max sequence length")] 182 | ExceededMaxDomainLen(usize), 183 | #[error("Authentication rejected `{0}`")] 184 | AuthenticationRejected(String), 185 | 186 | #[error(transparent)] 187 | ServerError(#[from] server::SocksServerError), 188 | 189 | #[error(transparent)] 190 | UdpHeaderError(#[from] UdpHeaderError), 191 | 192 | #[error(transparent)] 193 | AddrError(#[from] AddrError), 194 | 195 | #[error(transparent)] 196 | ConnectError(#[from] ConnectError), 197 | 198 | #[error("Error with reply: {0}.")] 199 | ReplyError(#[from] ReplyError), 200 | 201 | #[cfg(feature = "socks4")] 202 | #[error("Error with reply: {0}.")] 203 | ReplySocks4Error(#[from] socks4::ReplyError), 204 | 205 | #[error("Argument input error: `{0}`.")] 206 | ArgumentInputError(&'static str), 207 | 208 | // #[error("Other: `{0}`.")] 209 | #[error(transparent)] 210 | Other(#[from] anyhow::Error), 211 | } 212 | 213 | pub type Result = core::result::Result; 214 | 215 | /// SOCKS5 reply code 216 | #[derive(Error, Debug, Copy, Clone)] 217 | pub enum ReplyError { 218 | #[error("Succeeded")] 219 | Succeeded, 220 | #[error("General failure")] 221 | GeneralFailure, 222 | #[error("Connection not allowed by ruleset")] 223 | ConnectionNotAllowed, 224 | #[error("Network unreachable")] 225 | NetworkUnreachable, 226 | #[error("Host unreachable")] 227 | HostUnreachable, 228 | #[error("Connection refused")] 229 | ConnectionRefused, 230 | #[error("Connection timeout")] 231 | ConnectionTimeout, 232 | #[error("TTL expired")] 233 | TtlExpired, 234 | #[error("Command not supported")] 235 | CommandNotSupported, 236 | #[error("Address type not supported")] 237 | AddressTypeNotSupported, 238 | // OtherReply(u8), 239 | } 240 | 241 | impl ReplyError { 242 | #[inline] 243 | #[rustfmt::skip] 244 | pub fn as_u8(self) -> u8 { 245 | match self { 246 | ReplyError::Succeeded => consts::SOCKS5_REPLY_SUCCEEDED, 247 | ReplyError::GeneralFailure => consts::SOCKS5_REPLY_GENERAL_FAILURE, 248 | ReplyError::ConnectionNotAllowed => consts::SOCKS5_REPLY_CONNECTION_NOT_ALLOWED, 249 | ReplyError::NetworkUnreachable => consts::SOCKS5_REPLY_NETWORK_UNREACHABLE, 250 | ReplyError::HostUnreachable => consts::SOCKS5_REPLY_HOST_UNREACHABLE, 251 | ReplyError::ConnectionRefused => consts::SOCKS5_REPLY_CONNECTION_REFUSED, 252 | ReplyError::ConnectionTimeout => consts::SOCKS5_REPLY_TTL_EXPIRED, 253 | ReplyError::TtlExpired => consts::SOCKS5_REPLY_TTL_EXPIRED, 254 | ReplyError::CommandNotSupported => consts::SOCKS5_REPLY_COMMAND_NOT_SUPPORTED, 255 | ReplyError::AddressTypeNotSupported => consts::SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED, 256 | // ReplyError::OtherReply(c) => c, 257 | } 258 | } 259 | 260 | #[inline] 261 | #[rustfmt::skip] 262 | pub fn from_u8(code: u8) -> ReplyError { 263 | match code { 264 | consts::SOCKS5_REPLY_SUCCEEDED => ReplyError::Succeeded, 265 | consts::SOCKS5_REPLY_GENERAL_FAILURE => ReplyError::GeneralFailure, 266 | consts::SOCKS5_REPLY_CONNECTION_NOT_ALLOWED => ReplyError::ConnectionNotAllowed, 267 | consts::SOCKS5_REPLY_NETWORK_UNREACHABLE => ReplyError::NetworkUnreachable, 268 | consts::SOCKS5_REPLY_HOST_UNREACHABLE => ReplyError::HostUnreachable, 269 | consts::SOCKS5_REPLY_CONNECTION_REFUSED => ReplyError::ConnectionRefused, 270 | consts::SOCKS5_REPLY_TTL_EXPIRED => ReplyError::TtlExpired, 271 | consts::SOCKS5_REPLY_COMMAND_NOT_SUPPORTED => ReplyError::CommandNotSupported, 272 | consts::SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED => ReplyError::AddressTypeNotSupported, 273 | // _ => ReplyError::OtherReply(code), 274 | _ => unreachable!("ReplyError code unsupported."), 275 | } 276 | } 277 | } 278 | 279 | #[derive(thiserror::Error, Debug)] 280 | pub enum UdpHeaderError { 281 | #[error(transparent)] 282 | AddrError(#[from] AddrError), 283 | #[error("could not convert target addr: {0}")] 284 | ToTargetAddr(#[source] io::Error), 285 | #[error("could not read UDP header: {0}")] 286 | ReadingError(#[source] io::Error), 287 | #[error("does not match the expected reserved field")] 288 | GarbageInReserved, 289 | } 290 | 291 | /// Generate UDP header 292 | /// 293 | /// # UDP Request header structure. 294 | /// ```text 295 | /// +----+------+------+----------+----------+----------+ 296 | /// |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 297 | /// +----+------+------+----------+----------+----------+ 298 | /// | 2 | 1 | 1 | Variable | 2 | Variable | 299 | /// +----+------+------+----------+----------+----------+ 300 | /// 301 | /// The fields in the UDP request header are: 302 | /// 303 | /// o RSV Reserved X'0000' 304 | /// o FRAG Current fragment number 305 | /// o ATYP address type of following addresses: 306 | /// o IP V4 address: X'01' 307 | /// o DOMAINNAME: X'03' 308 | /// o IP V6 address: X'04' 309 | /// o DST.ADDR desired destination address 310 | /// o DST.PORT desired destination port 311 | /// o DATA user data 312 | /// ``` 313 | pub fn new_udp_header(target_addr: T) -> Result, UdpHeaderError> { 314 | let mut header = vec![ 315 | 0, 0, // RSV 316 | 0, // FRAG 317 | ]; 318 | header.append( 319 | &mut target_addr 320 | .to_target_addr() 321 | .map_err(UdpHeaderError::ToTargetAddr)? 322 | .to_be_bytes()?, 323 | ); 324 | 325 | Ok(header) 326 | } 327 | 328 | /// Parse data from UDP client on raw buffer, return (frag, target_addr, payload). 329 | pub async fn parse_udp_request<'a>( 330 | mut req: &'a [u8], 331 | ) -> Result<(u8, TargetAddr, &'a [u8]), UdpHeaderError> { 332 | let rsv = read_exact!(req, [0u8; 2]).map_err(UdpHeaderError::ReadingError)?; 333 | 334 | if !rsv.eq(&[0u8; 2]) { 335 | return Err(UdpHeaderError::GarbageInReserved); 336 | } 337 | 338 | let [frag, atyp] = read_exact!(req, [0u8; 2]).map_err(UdpHeaderError::ReadingError)?; 339 | 340 | let target_addr = read_address(&mut req, atyp).await?; 341 | 342 | Ok((frag, target_addr, req)) 343 | } 344 | 345 | #[cfg(test)] 346 | mod test { 347 | use anyhow::Result; 348 | use tokio::{ 349 | net::{TcpListener, TcpStream, UdpSocket}, 350 | sync::oneshot::Sender, 351 | }; 352 | 353 | use crate::{client, server, ReplyError, Socks5Command}; 354 | use std::{ 355 | net::{SocketAddr, ToSocketAddrs}, 356 | num::ParseIntError, 357 | }; 358 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 359 | use tokio::sync::oneshot; 360 | use tokio_test::block_on; 361 | 362 | fn init() { 363 | let _ = env_logger::builder().is_test(true).try_init(); 364 | } 365 | 366 | async fn setup_socks_server(proxy_addr: &str, tx: Sender) -> Result<()> { 367 | let reply_ip = proxy_addr.parse::().unwrap().ip(); 368 | 369 | let listener = TcpListener::bind(proxy_addr).await?; 370 | tx.send(listener.local_addr()?).unwrap(); 371 | 372 | loop { 373 | let (stream, _) = listener.accept().await?; // NOTE: not spawning for test 374 | let proto = server::Socks5ServerProtocol::accept_no_auth(stream).await?; 375 | let (proto, cmd, mut target_addr) = proto.read_command().await?; 376 | target_addr = target_addr.resolve_dns().await?; 377 | match cmd { 378 | Socks5Command::TCPConnect => { 379 | server::run_tcp_proxy(proto, &target_addr, 10, false).await?; 380 | } 381 | Socks5Command::UDPAssociate => { 382 | server::run_udp_proxy(proto, &target_addr, None, reply_ip, None).await?; 383 | } 384 | Socks5Command::TCPBind => { 385 | proto.reply_error(&ReplyError::CommandNotSupported).await?; 386 | } 387 | } 388 | } 389 | } 390 | 391 | async fn google(mut socket: TcpStream) -> Result<()> { 392 | socket.write_all(b"GET / HTTP/1.0\r\n\r\n").await?; 393 | let mut result = vec![]; 394 | socket.read_to_end(&mut result).await?; 395 | 396 | println!("{}", String::from_utf8_lossy(&result)); 397 | assert!(result.starts_with(b"HTTP/1.0")); 398 | assert!(result.ends_with(b"\r\n") || result.ends_with(b"")); 399 | 400 | Ok(()) 401 | } 402 | 403 | #[test] 404 | fn google_no_auth() { 405 | init(); 406 | block_on(async { 407 | let (tx, rx) = oneshot::channel(); 408 | tokio::spawn(setup_socks_server("[::1]:0", tx)); 409 | 410 | let socket = client::Socks5Stream::connect( 411 | rx.await.unwrap(), 412 | "google.com".to_owned(), 413 | 80, 414 | client::Config::default(), 415 | ) 416 | .await 417 | .unwrap(); 418 | google(socket.get_socket()).await.unwrap(); 419 | }); 420 | } 421 | 422 | #[test] 423 | fn mock_udp_assosiate_no_auth() { 424 | init(); 425 | block_on(async { 426 | const MOCK_ADDRESS: &str = "[::1]:40235"; 427 | 428 | let (tx, rx) = oneshot::channel(); 429 | tokio::spawn(setup_socks_server("[::1]:0", tx)); 430 | let backing_socket = TcpStream::connect(rx.await.unwrap()).await.unwrap(); 431 | 432 | // Creates a UDP tunnel which can be used to forward UDP packets, "[::]:0" indicates the 433 | // binding source address used to communicate with the socks5 server. 434 | let tunnel = client::Socks5Datagram::bind(backing_socket, "[::]:0") 435 | .await 436 | .unwrap(); 437 | let mock_udp_server = UdpSocket::bind(MOCK_ADDRESS).await.unwrap(); 438 | 439 | tunnel 440 | .send_to( 441 | b"hello world!", 442 | MOCK_ADDRESS.to_socket_addrs().unwrap().next().unwrap(), 443 | ) 444 | .await 445 | .unwrap(); 446 | println!("Send packet to {}", MOCK_ADDRESS); 447 | 448 | let mut buf = [0; 13]; 449 | let (len, addr) = mock_udp_server.recv_from(&mut buf).await.unwrap(); 450 | assert_eq!(len, 12); 451 | assert_eq!(&buf[..12], b"hello world!"); 452 | 453 | mock_udp_server 454 | .send_to(b"hello world!", addr) 455 | .await 456 | .unwrap(); 457 | 458 | println!("Recieve packet from {}", MOCK_ADDRESS); 459 | let len = tunnel.recv_from(&mut buf).await.unwrap().0; 460 | assert_eq!(len, 12); 461 | assert_eq!(&buf[..12], b"hello world!"); 462 | }); 463 | } 464 | 465 | #[test] 466 | fn dns_udp_assosiate_no_auth() { 467 | init(); 468 | block_on(async { 469 | const DNS_SERVER: &str = "1.1.1.1:53"; 470 | 471 | let (tx, rx) = oneshot::channel(); 472 | tokio::spawn(setup_socks_server("[::1]:0", tx)); 473 | let backing_socket = TcpStream::connect(rx.await.unwrap()).await.unwrap(); 474 | 475 | // Creates a UDP tunnel which can be used to forward UDP packets, "[::]:0" indicates the 476 | // binding source address used to communicate with the socks5 server. 477 | let tunnel = client::Socks5Datagram::bind(backing_socket, "[::]:0") 478 | .await 479 | .unwrap(); 480 | 481 | #[rustfmt::skip] 482 | tunnel.send_to( 483 | &decode_hex(&( 484 | "AAAA".to_owned() // ID 485 | + "0100" // Query parameters 486 | + "0001" // Number of questions 487 | + "0000" // Number of answers 488 | + "0000" // Number of authority records 489 | + "0000" // Number of additional records 490 | + "076578616d706c65"// Length + hex("example") 491 | + "03636f6d00" // Length + hex("com") + zero byte 492 | + "0001" // QTYPE 493 | + "0001" // QCLASS 494 | )) 495 | .unwrap(), 496 | DNS_SERVER.to_socket_addrs().unwrap().next().unwrap(), 497 | ).await.unwrap(); 498 | println!("Send packet to {}", DNS_SERVER); 499 | 500 | let mut buf = [0; 128]; 501 | println!("Recieve packet from {}", DNS_SERVER); 502 | tunnel.recv_from(&mut buf).await.unwrap(); 503 | println!("dns response {:?}", buf); 504 | 505 | #[rustfmt::skip] 506 | assert!(buf.starts_with(&decode_hex(&( 507 | "AAAA".to_owned() // ID 508 | + "8180" // FLAGS: RCODE=0, No errors reported 509 | + "0001" // One question 510 | )).unwrap())); 511 | }); 512 | } 513 | 514 | fn decode_hex(s: &str) -> Result, ParseIntError> { 515 | (0..s.len()) 516 | .step_by(2) 517 | .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) 518 | .collect() 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::util::stream::{tcp_connect_with_timeout, ConnectError}; 2 | use crate::util::target_addr::{read_address, AddrError, TargetAddr}; 3 | use crate::{ 4 | consts, new_udp_header, parse_udp_request, read_exact, ready, AuthenticationMethod, ReplyError, 5 | Socks5Command, SocksError, UdpHeaderError, 6 | }; 7 | use anyhow::Context; 8 | use socket2::{Domain, Socket, Type}; 9 | use std::future::Future; 10 | use std::io; 11 | use std::marker::PhantomData; 12 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs as StdToSocketAddrs}; 13 | use std::ops::Deref; 14 | use std::pin::Pin; 15 | use std::string::FromUtf8Error; 16 | use std::sync::Arc; 17 | use std::task::{Context as AsyncContext, Poll}; 18 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 19 | use tokio::net::{TcpListener, TcpStream, ToSocketAddrs as AsyncToSocketAddrs, UdpSocket}; 20 | use tokio::try_join; 21 | use tokio_stream::Stream; 22 | 23 | #[derive(thiserror::Error, Debug)] 24 | pub enum SocksServerError { 25 | #[error("i/o error when {context}: {source}")] 26 | Io { 27 | source: io::Error, 28 | context: &'static str, 29 | }, 30 | #[error("string error when {context}: {source}")] 31 | FromUtf8 { 32 | source: FromUtf8Error, 33 | context: &'static str, 34 | }, 35 | #[error(transparent)] 36 | ConnectError(#[from] ConnectError), 37 | #[error(transparent)] 38 | UdpHeaderError(#[from] UdpHeaderError), 39 | #[error(transparent)] 40 | AddrError(#[from] AddrError), 41 | #[error("BUG: {0}")] // should be unreachable 42 | Bug(&'static str), 43 | #[error("Auth method unacceptable `{0:?}`.")] 44 | AuthMethodUnacceptable(Vec), 45 | #[error("Unsupported SOCKS version `{0}`.")] 46 | UnsupportedSocksVersion(u8), 47 | #[error("Unsupported SOCKS command `{0}`.")] 48 | UnknownCommand(u8), 49 | #[error("Unexpected garbage received on TCP stream used for UDP proxy keep-alive: `{0}`")] 50 | UnexpectedUdpControlGarbage(u8), 51 | #[error("Empty username received")] 52 | EmptyUsername, 53 | #[error("Empty password received")] 54 | EmptyPassword, 55 | #[error("Authentication rejected")] 56 | AuthenticationRejected, 57 | #[error("End of stream")] 58 | EOF, 59 | } 60 | 61 | impl SocksServerError { 62 | pub fn to_reply_error(&self) -> ReplyError { 63 | match self { 64 | SocksServerError::UnknownCommand(_) => ReplyError::CommandNotSupported, 65 | SocksServerError::AddrError(err) => err.to_reply_error(), 66 | _ => ReplyError::GeneralFailure, 67 | } 68 | } 69 | } 70 | 71 | pub trait ErrorContext { 72 | fn err_when(self, context: &'static str) -> Result; 73 | } 74 | 75 | impl ErrorContext for Result { 76 | fn err_when(self, context: &'static str) -> Result { 77 | self.map_err(|source| SocksServerError::Io { source, context }) 78 | } 79 | } 80 | 81 | impl ErrorContext for Result { 82 | fn err_when(self, context: &'static str) -> Result { 83 | self.map_err(|source| SocksServerError::FromUtf8 { source, context }) 84 | } 85 | } 86 | 87 | #[derive(Clone)] 88 | pub struct Config { 89 | /// Timeout of the command request 90 | request_timeout: u64, 91 | /// Avoid useless roundtrips if we don't need the Authentication layer 92 | skip_auth: bool, 93 | /// Enable dns-resolving 94 | dns_resolve: bool, 95 | /// Enable command execution 96 | execute_command: bool, 97 | /// Enable UDP support 98 | allow_udp: bool, 99 | /// For some complex scenarios, we may want to either accept Username/Password configuration 100 | /// or IP Whitelisting, in case the client send only 1-2 auth methods (no auth) rather than 3 (with auth) 101 | allow_no_auth: bool, 102 | /// Contains the authentication trait to use the user against with 103 | auth: Option>, 104 | /// Disables Nagle's algorithm for TCP 105 | nodelay: bool, 106 | } 107 | 108 | impl Default for Config { 109 | fn default() -> Self { 110 | Config { 111 | request_timeout: 10, 112 | skip_auth: false, 113 | dns_resolve: true, 114 | execute_command: true, 115 | allow_udp: false, 116 | allow_no_auth: false, 117 | auth: None, 118 | nodelay: false, 119 | } 120 | } 121 | } 122 | 123 | /// Use this trait to handle a custom authentication on your end. 124 | #[async_trait::async_trait] 125 | pub trait Authentication: Send + Sync { 126 | type Item; 127 | 128 | async fn authenticate(&self, credentials: Option<(String, String)>) -> Option; 129 | } 130 | 131 | async fn authenticate_callback( 132 | auth_callback: &A, 133 | auth: StandardAuthenticationStarted, 134 | ) -> Result<(Socks5ServerProtocol, A::Item), SocksServerError> { 135 | match auth { 136 | StandardAuthenticationStarted::NoAuthentication(auth) => { 137 | if let Some(credentials) = auth_callback.authenticate(None).await { 138 | Ok((auth.finish_auth(), credentials)) 139 | } else { 140 | Err(SocksServerError::AuthenticationRejected) 141 | } 142 | } 143 | StandardAuthenticationStarted::PasswordAuthentication(auth) => { 144 | let (username, password, auth) = auth.read_username_password().await?; 145 | if let Some(credentials) = auth_callback.authenticate(Some((username, password))).await 146 | { 147 | Ok((auth.accept().await?.finish_auth(), credentials)) 148 | } else { 149 | auth.reject().await?; 150 | Err(SocksServerError::AuthenticationRejected) 151 | } 152 | } 153 | } 154 | } 155 | 156 | /// Basic user/pass auth method provided. 157 | pub struct SimpleUserPassword { 158 | pub username: String, 159 | pub password: String, 160 | } 161 | 162 | /// The struct returned when the user has successfully authenticated 163 | pub struct AuthSucceeded { 164 | pub username: String, 165 | } 166 | 167 | /// This is an example to auth via simple credentials. 168 | /// If the auth succeed, we return the username authenticated with, for further uses. 169 | #[async_trait::async_trait] 170 | impl Authentication for SimpleUserPassword { 171 | type Item = AuthSucceeded; 172 | 173 | async fn authenticate(&self, credentials: Option<(String, String)>) -> Option { 174 | if let Some((username, password)) = credentials { 175 | // Client has supplied credentials 176 | if username == self.username && password == self.password { 177 | // Some() will allow the authentication and the credentials 178 | // will be forwarded to the socket 179 | Some(AuthSucceeded { username }) 180 | } else { 181 | // Credentials incorrect, we deny the auth 182 | None 183 | } 184 | } else { 185 | // The client hasn't supplied any credentials, which only happens 186 | // when `Config::allow_no_auth()` is set as `true` 187 | None 188 | } 189 | } 190 | } 191 | 192 | /// This will simply return Option::None, which denies the authentication 193 | #[derive(Copy, Clone, Default)] 194 | pub struct DenyAuthentication {} 195 | 196 | #[async_trait::async_trait] 197 | impl Authentication for DenyAuthentication { 198 | type Item = (); 199 | 200 | async fn authenticate(&self, _credentials: Option<(String, String)>) -> Option { 201 | None 202 | } 203 | } 204 | 205 | /// While this one will always allow the user in. 206 | #[derive(Copy, Clone, Default)] 207 | pub struct AcceptAuthentication {} 208 | 209 | #[async_trait::async_trait] 210 | impl Authentication for AcceptAuthentication { 211 | type Item = (); 212 | 213 | async fn authenticate(&self, _credentials: Option<(String, String)>) -> Option { 214 | Some(()) 215 | } 216 | } 217 | 218 | impl Config { 219 | /// How much time it should wait until the request timeout. 220 | pub fn set_request_timeout(&mut self, n: u64) -> &mut Self { 221 | self.request_timeout = n; 222 | self 223 | } 224 | 225 | /// Skip the entire auth/handshake part, which means the server will directly wait for 226 | /// the command request. 227 | pub fn set_skip_auth(&mut self, value: bool) -> &mut Self { 228 | self.skip_auth = value; 229 | self.auth = None; 230 | self 231 | } 232 | 233 | /// Enable authentication 234 | /// 'static lifetime for Authentication avoid us to use `dyn Authentication` 235 | /// and set the Arc before calling the function. 236 | pub fn with_authentication(self, authentication: T) -> Config { 237 | Config { 238 | request_timeout: self.request_timeout, 239 | skip_auth: self.skip_auth, 240 | dns_resolve: self.dns_resolve, 241 | execute_command: self.execute_command, 242 | allow_udp: self.allow_udp, 243 | allow_no_auth: self.allow_no_auth, 244 | auth: Some(Arc::new(authentication)), 245 | nodelay: self.nodelay, 246 | } 247 | } 248 | 249 | /// For some complex scenarios, we may want to either accept Username/Password configuration 250 | /// or IP Whitelisting, in case the client send only 2 auth methods rather than 3 (with auth) 251 | pub fn set_allow_no_auth(&mut self, value: bool) -> &mut Self { 252 | self.allow_no_auth = value; 253 | self 254 | } 255 | 256 | /// Set whether or not to execute commands 257 | pub fn set_execute_command(&mut self, value: bool) -> &mut Self { 258 | self.execute_command = value; 259 | self 260 | } 261 | 262 | /// Will the server perform dns resolve 263 | pub fn set_dns_resolve(&mut self, value: bool) -> &mut Self { 264 | self.dns_resolve = value; 265 | self 266 | } 267 | 268 | /// Set whether or not to allow udp traffic 269 | pub fn set_udp_support(&mut self, value: bool) -> &mut Self { 270 | self.allow_udp = value; 271 | self 272 | } 273 | } 274 | 275 | /// Wrapper of TcpListener 276 | /// Useful if you don't use any existing TcpListener's streams. 277 | #[deprecated( 278 | since = "0.11.0", 279 | note = "Use the new explicit API instead, see examples/server.rs" 280 | )] 281 | pub struct Socks5Server { 282 | listener: TcpListener, 283 | config: Arc>, 284 | } 285 | 286 | #[allow(deprecated)] 287 | impl Socks5Server { 288 | pub async fn bind(addr: S) -> io::Result { 289 | let listener = TcpListener::bind(&addr).await?; 290 | let config = Arc::new(Config::default()); 291 | 292 | Ok(Socks5Server { listener, config }) 293 | } 294 | } 295 | 296 | #[allow(deprecated)] 297 | impl Socks5Server { 298 | /// Set a custom config 299 | pub fn with_config(self, config: Config) -> Socks5Server { 300 | Socks5Server { 301 | listener: self.listener, 302 | config: Arc::new(config), 303 | } 304 | } 305 | 306 | /// Can loop on `incoming().next()` to iterate over incoming connections. 307 | pub fn incoming(&self) -> Incoming<'_, A> { 308 | Incoming(self, None) 309 | } 310 | } 311 | 312 | /// `Incoming` implements [`futures_core::stream::Stream`]. 313 | /// 314 | /// [`futures_core::stream::Stream`]: https://docs.rs/futures/0.3.30/futures/stream/trait.Stream.html 315 | #[allow(deprecated)] 316 | pub struct Incoming<'a, A: Authentication>( 317 | &'a Socks5Server, 318 | Option> + Send + Sync + 'a>>>, 319 | ); 320 | 321 | /// Iterator for each incoming stream connection 322 | /// this wrapper will convert async_std TcpStream into Socks5Socket. 323 | #[allow(deprecated)] 324 | impl<'a, A: Authentication> Stream for Incoming<'a, A> { 325 | type Item = Result, SocksError>; 326 | 327 | /// this code is mainly borrowed from [`Incoming::poll_next()` of `TcpListener`][tcpListenerLink] 328 | /// 329 | /// [tcpListenerLink]: https://docs.rs/async-std/1.8.0/async_std/net/struct.TcpListener.html#method.incoming 330 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut AsyncContext<'_>) -> Poll> { 331 | loop { 332 | if self.1.is_none() { 333 | self.1 = Some(Box::pin(self.0.listener.accept())); 334 | } 335 | 336 | if let Some(f) = &mut self.1 { 337 | // early returns if pending 338 | let (socket, peer_addr) = ready!(f.as_mut().poll(cx))?; 339 | self.1 = None; 340 | 341 | let local_addr = socket.local_addr()?; 342 | debug!( 343 | "incoming connection from peer {} @ {}", 344 | &peer_addr, &local_addr 345 | ); 346 | 347 | // Wrap the TcpStream into Socks5Socket 348 | let socket = Socks5Socket::new(socket, self.0.config.clone()); 349 | 350 | return Poll::Ready(Some(Ok(socket))); 351 | } 352 | } 353 | } 354 | } 355 | 356 | /// Wrap TcpStream and contains Socks5 protocol implementation. 357 | #[deprecated( 358 | since = "0.11.0", 359 | note = "Use the new explicit API instead, see examples/server.rs" 360 | )] 361 | pub struct Socks5Socket { 362 | inner: T, 363 | config: Arc>, 364 | auth: AuthenticationMethod, 365 | target_addr: Option, 366 | cmd: Option, 367 | /// Socket address which will be used in the reply message. 368 | reply_ip: Option, 369 | /// If the client has been authenticated, that's where we store his credentials 370 | /// to be accessed from the socket 371 | credentials: Option, 372 | } 373 | 374 | pub mod states { 375 | pub struct Opened; 376 | pub struct Authenticated; 377 | pub struct CommandRead; 378 | } 379 | 380 | pub struct Socks5ServerProtocol { 381 | inner: T, 382 | _state: PhantomData, 383 | } 384 | 385 | impl Socks5ServerProtocol { 386 | fn new(inner: T) -> Self { 387 | Socks5ServerProtocol { 388 | inner, 389 | _state: PhantomData, 390 | } 391 | } 392 | } 393 | 394 | impl Socks5ServerProtocol { 395 | /// Start handling the SOCKS5 protocol flow, wrapping a client socket. 396 | pub fn start(inner: T) -> Self { 397 | Self::new(inner) 398 | } 399 | } 400 | 401 | pub trait CheckResult { 402 | fn is_good(&self) -> bool; 403 | } 404 | 405 | impl CheckResult for bool { 406 | fn is_good(&self) -> bool { 407 | *self 408 | } 409 | } 410 | 411 | impl CheckResult for Option { 412 | fn is_good(&self) -> bool { 413 | self.is_some() 414 | } 415 | } 416 | 417 | impl CheckResult for Result { 418 | fn is_good(&self) -> bool { 419 | self.is_ok() 420 | } 421 | } 422 | 423 | impl Socks5ServerProtocol { 424 | /// Finish handling the authentication method-specific part of the protocol, 425 | /// returning back to the overall SOCKS5 flow. 426 | pub fn finish_auth>(auth: A) -> Self { 427 | Self::new(auth.into_inner()) 428 | } 429 | 430 | /// Wrap a socket in a SOCKS5 flow handler that's already marked as authenticated. 431 | /// 432 | /// This is not actually part of the official SOCKS5 protocol, but allows you to 433 | /// only use the post-authentication subset of it. 434 | pub fn skip_auth_this_is_not_rfc_compliant(inner: T) -> Self { 435 | Self::new(inner) 436 | } 437 | 438 | /// Handle the SOCKS5 auth negotiation supporting only the `NoAuthentication` method. 439 | pub async fn accept_no_auth(inner: T) -> Result 440 | where 441 | T: AsyncWrite + AsyncRead + Unpin, 442 | { 443 | Ok(Socks5ServerProtocol::start(inner) 444 | .negotiate_auth(&[NoAuthentication]) 445 | .await? 446 | .finish_auth()) 447 | } 448 | 449 | /// Handle the SOCKS5 auth negotiation supporting only the `PasswordAuthentication` method, 450 | /// and verify the provided username and password using the provided closure. 451 | /// 452 | /// The closure can mutate state variables and/or return a result as `Option`/`Result`. 453 | pub async fn accept_password_auth( 454 | inner: T, 455 | mut check: F, 456 | ) -> Result<(Self, R), SocksServerError> 457 | where 458 | T: AsyncWrite + AsyncRead + Unpin, 459 | F: FnMut(String, String) -> R, 460 | R: CheckResult, 461 | { 462 | let (user, pass, auth) = Socks5ServerProtocol::start(inner) 463 | .negotiate_auth(&[PasswordAuthentication]) 464 | .await? 465 | .read_username_password() 466 | .await?; 467 | let check_result = check(user, pass); 468 | if check_result.is_good() { 469 | Ok((auth.accept().await?.finish_auth(), check_result)) 470 | } else { 471 | auth.reject().await?; 472 | Err(SocksServerError::AuthenticationRejected) 473 | } 474 | } 475 | } 476 | 477 | /// A trait for the final successful state of an authentication method's implementation. 478 | /// 479 | /// This allows `Socks5ServerProtocol::finish_authentication` to 480 | /// let the user continue with the protocol after the socket has been handed off to the 481 | /// authentication method. 482 | pub trait AuthMethodSuccessState { 483 | fn into_inner(self) -> T; 484 | 485 | fn finish_auth(self) -> Socks5ServerProtocol 486 | where 487 | Self: Sized, 488 | { 489 | Socks5ServerProtocol::finish_auth(self) 490 | } 491 | } 492 | 493 | /// A metadata trait for authentication methods, essentially binding an ID value 494 | /// (as used in the method negotiation) to an actual implementation of the method. 495 | /// 496 | /// Use blank structs for individual protocol implementations and 497 | /// enums for sets of supported protocols (you'll need a matching enum for the `Impl`). 498 | pub trait AuthMethod: Copy { 499 | type StartingState; 500 | fn method_id(self) -> u8; 501 | fn new(self, inner: T) -> Self::StartingState; 502 | } 503 | 504 | pub struct NoAuthenticationImpl(T); 505 | 506 | impl AuthMethodSuccessState for NoAuthenticationImpl { 507 | fn into_inner(self) -> T { 508 | self.0 509 | } 510 | } 511 | 512 | /// The "NO AUTHENTICATION REQUIRED" auth method, ID 00h as specifed by RFC 1928. 513 | /// 514 | /// As the dummy no-auth method, it only has one state. Once it's been negotiated, 515 | /// you can immediately continue with `finish_authentication`. 516 | /// 517 | /// Or not so immediately: if you want to use no-authentication with e.g. IP address 518 | /// allowlisting or TLS client certificate auth for TLS-wrapped SOCKS5, this is your 519 | /// opportunity to reject the no-authentication by dropping the connection! 520 | #[derive(Debug, Clone, Copy)] 521 | pub struct NoAuthentication; 522 | 523 | impl AuthMethod for NoAuthentication { 524 | type StartingState = NoAuthenticationImpl; 525 | 526 | fn method_id(self) -> u8 { 527 | 0x00 528 | } 529 | 530 | fn new(self, inner: T) -> Self::StartingState { 531 | NoAuthenticationImpl(inner) 532 | } 533 | } 534 | 535 | mod password_states { 536 | pub struct Started; 537 | pub struct Received; 538 | pub struct Finished; 539 | } 540 | 541 | pub struct PasswordAuthenticationImpl { 542 | inner: T, 543 | _state: PhantomData, 544 | } 545 | 546 | pub type PasswordAuthenticationStarted = PasswordAuthenticationImpl; 547 | 548 | impl PasswordAuthenticationImpl { 549 | fn new(inner: T) -> Self { 550 | PasswordAuthenticationImpl { 551 | inner, 552 | _state: PhantomData, 553 | } 554 | } 555 | } 556 | 557 | impl PasswordAuthenticationImpl { 558 | /// Handle the username and password sent by the client. 559 | pub async fn read_username_password( 560 | self, 561 | ) -> Result< 562 | ( 563 | String, 564 | String, 565 | PasswordAuthenticationImpl, 566 | ), 567 | SocksServerError, 568 | > { 569 | let mut socket = self.inner; 570 | trace!("PasswordAuthenticationStarted: read_username_password()"); 571 | let [version, user_len] = read_exact!(socket, [0u8; 2]).err_when("reading user len")?; 572 | debug!( 573 | "Auth: [version: {version}, user len: {len}]", 574 | version = version, 575 | len = user_len, 576 | ); 577 | 578 | if user_len < 1 { 579 | return Err(SocksServerError::EmptyUsername); 580 | } 581 | 582 | let username = 583 | read_exact!(socket, vec![0u8; user_len as usize]).err_when("reading username")?; 584 | debug!("username bytes: {:?}", &username); 585 | 586 | let [pass_len] = read_exact!(socket, [0u8; 1]).err_when("reading password len")?; 587 | debug!("Auth: [pass len: {len}]", len = pass_len,); 588 | 589 | if pass_len < 1 { 590 | return Err(SocksServerError::EmptyPassword); 591 | } 592 | 593 | let password = 594 | read_exact!(socket, vec![0u8; pass_len as usize]).err_when("reading password")?; 595 | debug!("password bytes: {:?}", &password); 596 | 597 | let username = String::from_utf8(username).err_when("converting username")?; 598 | let password = String::from_utf8(password).err_when("converting password")?; 599 | 600 | Ok((username, password, PasswordAuthenticationImpl::new(socket))) 601 | } 602 | } 603 | 604 | impl PasswordAuthenticationImpl { 605 | /// Notify the client with a "SUCCEEDED" reply and proceed to finish the authentication. 606 | pub async fn accept( 607 | mut self, 608 | ) -> Result, SocksServerError> { 609 | self.inner 610 | .write_all(&[1, consts::SOCKS5_REPLY_SUCCEEDED]) 611 | .await 612 | .err_when("replying auth success")?; 613 | 614 | info!("Password authentication accepted."); 615 | Ok(PasswordAuthenticationImpl::new(self.inner)) 616 | } 617 | 618 | /// Notify the client with a "NOT_ACCEPTABLE" reply and drop the socket. 619 | pub async fn reject(mut self) -> Result<(), SocksServerError> { 620 | self.inner 621 | .write_all(&[1, consts::SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE]) 622 | .await 623 | .err_when("replying with auth method not acceptable")?; 624 | 625 | info!("Password authentication rejected."); 626 | Ok(()) 627 | } 628 | } 629 | 630 | impl AuthMethodSuccessState for PasswordAuthenticationImpl { 631 | fn into_inner(self) -> T { 632 | self.inner 633 | } 634 | } 635 | 636 | /// The "USERNAME/PASSWORD" auth method, ID 02h as specified by RFC 1928. 637 | #[derive(Debug, Clone, Copy)] 638 | pub struct PasswordAuthentication; 639 | 640 | impl AuthMethod for PasswordAuthentication { 641 | type StartingState = PasswordAuthenticationImpl; 642 | 643 | fn method_id(self) -> u8 { 644 | 0x02 645 | } 646 | 647 | fn new(self, inner: T) -> Self::StartingState { 648 | PasswordAuthenticationImpl::new(inner) 649 | } 650 | } 651 | 652 | #[macro_export] 653 | macro_rules! auth_method_enums { 654 | ( 655 | $(#[$enum_meta:meta])* 656 | $vis:vis enum $enum:ident / $(#[$state_enum_meta:meta])* $state_enum:ident<$state_enum_par:ident> { 657 | $($method:ident($state:ty)),+ $(,)? 658 | } 659 | ) => { 660 | $(#[$state_enum_meta])* 661 | $vis enum $state_enum<$state_enum_par> { 662 | $($method($state)),+ 663 | } 664 | 665 | #[derive(Clone, Copy)] 666 | $(#[$enum_meta])* 667 | $vis enum $enum { 668 | $($method($method)),+ 669 | } 670 | 671 | impl AuthMethod for $enum { 672 | type StartingState = $state_enum; 673 | 674 | fn method_id(self) -> u8 { 675 | match self { 676 | $($enum::$method(auth) => AuthMethod::::method_id(auth)),+ 677 | } 678 | } 679 | 680 | fn new(self, inner: T) -> Self::StartingState { 681 | match self { 682 | $($enum::$method(auth) => $state_enum::$method(auth.new(inner))),+ 683 | } 684 | } 685 | } 686 | }; 687 | } 688 | 689 | auth_method_enums! { 690 | /// The combination of all authentication methods supported by this crate out of the box, 691 | /// as an enum appropriate for static dispatch. 692 | /// 693 | /// If you want to add your own custom methods, you can generate a similar enum using the `auth_method_enums` macro. 694 | pub enum StandardAuthentication / StandardAuthenticationStarted { 695 | NoAuthentication(NoAuthenticationImpl), 696 | PasswordAuthentication(PasswordAuthenticationImpl), 697 | } 698 | } 699 | 700 | impl StandardAuthentication { 701 | /// Return a slice containing either both supported methods or only `PasswordAuthentication`. 702 | pub fn allow_no_auth(allow: bool) -> &'static [StandardAuthentication] { 703 | if allow { 704 | &[ 705 | // The order of authentication methods can be tested by clients in sequence, 706 | // so list more secure or preferred methods first 707 | StandardAuthentication::PasswordAuthentication(PasswordAuthentication), 708 | StandardAuthentication::NoAuthentication(NoAuthentication), 709 | ] 710 | } else { 711 | &[StandardAuthentication::PasswordAuthentication( 712 | PasswordAuthentication, 713 | )] 714 | } 715 | } 716 | } 717 | 718 | #[allow(deprecated)] 719 | impl Socks5Socket { 720 | pub fn new(socket: T, config: Arc>) -> Self { 721 | Socks5Socket { 722 | inner: socket, 723 | config, 724 | auth: AuthenticationMethod::None, 725 | target_addr: None, 726 | cmd: None, 727 | reply_ip: None, 728 | credentials: None, 729 | } 730 | } 731 | 732 | /// Set the bind IP address in Socks5Reply. 733 | /// 734 | /// Only the inner socket owner knows the correct reply bind addr, so leave this field to be 735 | /// populated. For those strict clients, users can use this function to set the correct IP 736 | /// address. 737 | /// 738 | /// Most popular SOCKS5 clients [1] [2] ignore BND.ADDR and BND.PORT the reply of command 739 | /// CONNECT, but this field could be useful in some other command, such as UDP ASSOCIATE. 740 | /// 741 | /// [1]: https://github.com/chromium/chromium/blob/bd2c7a8b65ec42d806277dd30f138a673dec233a/net/socket/socks5_client_socket.cc#L481 742 | /// [2]: https://github.com/curl/curl/blob/d15692ebbad5e9cfb871b0f7f51a73e43762cee2/lib/socks.c#L978 743 | pub fn set_reply_ip(&mut self, addr: IpAddr) { 744 | self.reply_ip = Some(addr); 745 | } 746 | 747 | /// Process clients SOCKS requests 748 | /// This is the entry point where a whole request is processed. 749 | pub async fn upgrade_to_socks5(mut self) -> Result, SocksError> { 750 | trace!("upgrading to socks5..."); 751 | 752 | // NOTE: this cannot be split in two without making self.inner an Option 753 | 754 | // Handshake 755 | let proto = match self.config.auth.as_ref() { 756 | _ if self.config.skip_auth => { 757 | debug!("skipping auth"); 758 | Socks5ServerProtocol::skip_auth_this_is_not_rfc_compliant(self.inner) 759 | } 760 | None => Socks5ServerProtocol::start(self.inner) 761 | .negotiate_auth(&[NoAuthentication]) 762 | .await? 763 | .finish_auth(), 764 | Some(auth_callback) => { 765 | let methods = StandardAuthentication::allow_no_auth(self.config.allow_no_auth); 766 | let auth = Socks5ServerProtocol::start(self.inner) 767 | .negotiate_auth(methods) 768 | .await?; 769 | let (proto, creds) = authenticate_callback(auth_callback.as_ref(), auth).await?; 770 | self.credentials = Some(creds); 771 | proto 772 | } 773 | }; 774 | 775 | let (proto, cmd, target_addr) = { 776 | let triple = proto.read_command().await?; 777 | 778 | if self.config.dns_resolve { 779 | triple.resolve_dns().await? 780 | } else { 781 | debug!( 782 | "Domain won't be resolved because `dns_resolve`'s config has been turned off." 783 | ); 784 | triple 785 | } 786 | }; 787 | 788 | match cmd { 789 | cmd if !self.config.execute_command => { 790 | self.cmd = Some(cmd); 791 | self.inner = proto.inner; 792 | } 793 | Socks5Command::TCPConnect => { 794 | self.inner = run_tcp_proxy( 795 | proto, 796 | &target_addr, 797 | self.config.request_timeout, 798 | self.config.nodelay, 799 | ) 800 | .await?; 801 | } 802 | Socks5Command::UDPAssociate if self.config.allow_udp => { 803 | self.inner = run_udp_proxy( 804 | proto, 805 | &target_addr, 806 | None, 807 | self.reply_ip.context("invalid reply ip")?, 808 | None, 809 | ) 810 | .await?; 811 | } 812 | _ => { 813 | proto.reply_error(&ReplyError::CommandNotSupported).await?; 814 | return Err(ReplyError::CommandNotSupported.into()); 815 | } 816 | }; 817 | 818 | self.target_addr = Some(target_addr); /* legacy API leaves it exported */ 819 | Ok(self) 820 | } 821 | 822 | /// Consumes the `Socks5Socket`, returning the wrapped stream. 823 | pub fn into_inner(self) -> T { 824 | self.inner 825 | } 826 | 827 | /// This function is public, it can be call manually on your own-willing 828 | /// if config flag has been turned off: `Config::dns_resolve == false`. 829 | pub async fn resolve_dns(&mut self) -> Result<(), SocksError> { 830 | trace!("resolving dns"); 831 | if let Some(target_addr) = self.target_addr.take() { 832 | // decide whether we have to resolve DNS or not 833 | self.target_addr = match target_addr { 834 | TargetAddr::Domain(_, _) => Some(target_addr.resolve_dns().await?), 835 | TargetAddr::Ip(_) => Some(target_addr), 836 | }; 837 | } 838 | 839 | Ok(()) 840 | } 841 | 842 | pub fn target_addr(&self) -> Option<&TargetAddr> { 843 | self.target_addr.as_ref() 844 | } 845 | 846 | pub fn auth(&self) -> &AuthenticationMethod { 847 | &self.auth 848 | } 849 | 850 | pub fn cmd(&self) -> &Option { 851 | &self.cmd 852 | } 853 | 854 | /// Borrow the credentials of the user has authenticated with 855 | pub fn get_credentials(&self) -> Option<&<::Item as Deref>::Target> 856 | where 857 | ::Item: Deref, 858 | { 859 | self.credentials.as_deref() 860 | } 861 | 862 | /// Get the credentials of the user has authenticated with 863 | pub fn take_credentials(&mut self) -> Option { 864 | self.credentials.take() 865 | } 866 | } 867 | 868 | impl Socks5ServerProtocol { 869 | /// Negotiate an authentication method from a list of supported ones and initialize it. 870 | /// 871 | /// Internally, this reads the list of authentication methods provided by the client, and 872 | /// picks the first one for which there exists an implementation in `server_methods`. 873 | /// 874 | /// If none of the auth methods requested by the client are in `server_methods`, 875 | /// returns a `SocksServerError::AuthMethodUnacceptable`. 876 | pub async fn negotiate_auth>( 877 | mut self, 878 | server_methods: &[M], 879 | ) -> Result { 880 | trace!("Socks5ServerProtocol: negotiate_auth()"); 881 | let [version, methods_len] = 882 | read_exact!(self.inner, [0u8; 2]).err_when("reading methods")?; 883 | debug!( 884 | "Handshake headers: [version: {version}, methods len: {len}]", 885 | version = version, 886 | len = methods_len, 887 | ); 888 | 889 | if version != consts::SOCKS5_VERSION { 890 | return Err(SocksServerError::UnsupportedSocksVersion(version)); 891 | } 892 | 893 | // {METHODS available from the client} 894 | // eg. (non-auth) {0, 1} 895 | // eg. (auth) {0, 1, 2} 896 | let methods = 897 | read_exact!(self.inner, vec![0u8; methods_len as usize]).err_when("reading methods")?; 898 | debug!("methods supported sent by the client: {:?}", &methods); 899 | 900 | // server_methods order matter! 901 | // the server could choose to prioritize methods 902 | for server_method in server_methods { 903 | for client_method_id in methods.iter() { 904 | if server_method.method_id() == *client_method_id { 905 | debug!("Reply with method {}", *client_method_id); 906 | self.inner 907 | .write_all(&[consts::SOCKS5_VERSION, *client_method_id]) 908 | .await 909 | .err_when("replying with auth method")?; 910 | return Ok(server_method.new(self.inner)); 911 | } 912 | } 913 | } 914 | 915 | debug!("No auth method supported by both client and server, reply with (0xff)"); 916 | self.inner 917 | .write_all(&[ 918 | consts::SOCKS5_VERSION, 919 | consts::SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE, 920 | ]) 921 | .await 922 | .err_when("replying with method not acceptable")?; 923 | Err(SocksServerError::AuthMethodUnacceptable(methods)) 924 | } 925 | } 926 | 927 | impl Socks5ServerProtocol { 928 | /// Reply success to the client according to the RFC. 929 | /// This consumes the wrapper as after this message actual proxying should begin. 930 | pub async fn reply_success(mut self, sock_addr: SocketAddr) -> Result { 931 | self.inner 932 | .write(&new_reply(&ReplyError::Succeeded, sock_addr)) 933 | .await 934 | .err_when("writing successful reply")?; 935 | 936 | self.inner.flush().await.err_when("flushing auth reply")?; 937 | 938 | debug!("Wrote success"); 939 | Ok(self.inner) 940 | } 941 | 942 | /// Reply error to the client with the reply code according to the RFC. 943 | pub async fn reply_error(mut self, error: &ReplyError) -> Result<(), SocksServerError> { 944 | let reply = new_reply(error, "0.0.0.0:0".parse().unwrap()); 945 | debug!("reply error to be written: {:?}", &reply); 946 | 947 | self.inner 948 | .write(&reply) 949 | .await 950 | .err_when("writing unsuccessful reply")?; 951 | 952 | self.inner.flush().await.err_when("flushing auth reply")?; 953 | 954 | Ok(()) 955 | } 956 | } 957 | 958 | macro_rules! try_notify { 959 | ($proto:expr, $e:expr) => { 960 | match $e { 961 | Ok(res) => res, 962 | Err(err) => { 963 | if let Err(rep_err) = $proto.reply_error(&err.to_reply_error()).await { 964 | error!( 965 | "extra error while reporting an error to the client: {}", 966 | rep_err 967 | ); 968 | } 969 | return Err(err.into()); 970 | } 971 | } 972 | }; 973 | } 974 | 975 | impl Socks5ServerProtocol { 976 | /// Decide to whether or not, accept the authentication method. 977 | /// Don't forget that the methods list sent by the client, contains one or more methods. 978 | /// 979 | /// # Request 980 | /// ```text 981 | /// +----+-----+-------+------+----------+----------+ 982 | /// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 983 | /// +----+-----+-------+------+----------+----------+ 984 | /// | 1 | 1 | 1 | 1 | Variable | 2 | 985 | /// +----+-----+-------+------+----------+----------+ 986 | /// ``` 987 | /// 988 | /// It the request is correct, it should returns a ['SocketAddr']. 989 | /// 990 | pub async fn read_command( 991 | mut self, 992 | ) -> Result< 993 | ( 994 | Socks5ServerProtocol, 995 | Socks5Command, 996 | TargetAddr, 997 | ), 998 | SocksServerError, 999 | > { 1000 | let [version, cmd, rsv, address_type] = 1001 | read_exact!(self.inner, [0u8; 4]).err_when("reading command")?; 1002 | debug!( 1003 | "Request: [version: {version}, command: {cmd}, rev: {rsv}, address_type: {address_type}]", 1004 | version = version, 1005 | cmd = cmd, 1006 | rsv = rsv, 1007 | address_type = address_type, 1008 | ); 1009 | 1010 | if version != consts::SOCKS5_VERSION { 1011 | return Err(SocksServerError::UnsupportedSocksVersion(version)); 1012 | } 1013 | 1014 | let mut proto = Socks5ServerProtocol::new(self.inner); 1015 | 1016 | // Guess address type 1017 | let target_addr = try_notify!(proto, read_address(&mut proto.inner, address_type).await); 1018 | 1019 | debug!("Request target is {}", target_addr); 1020 | 1021 | let cmd = try_notify!( 1022 | proto, 1023 | Socks5Command::from_u8(cmd).ok_or(SocksServerError::UnknownCommand(cmd)) 1024 | ); 1025 | 1026 | Ok((proto, cmd, target_addr)) 1027 | } 1028 | } 1029 | 1030 | #[allow(async_fn_in_trait)] 1031 | pub trait DnsResolveHelper 1032 | where 1033 | Self: Sized, 1034 | { 1035 | async fn resolve_dns(self) -> Result; 1036 | } 1037 | 1038 | impl DnsResolveHelper 1039 | for ( 1040 | Socks5ServerProtocol, 1041 | Socks5Command, 1042 | TargetAddr, 1043 | ) 1044 | where 1045 | T: AsyncRead + AsyncWrite + Unpin, 1046 | { 1047 | async fn resolve_dns(self) -> Result { 1048 | let (proto, cmd, target_addr) = self; 1049 | let resolved_addr = try_notify!(proto, target_addr.resolve_dns().await); 1050 | Ok((proto, cmd, resolved_addr)) 1051 | } 1052 | } 1053 | 1054 | /// Handle the connect command by running a TCP proxy until the connection is done. 1055 | pub async fn run_tcp_proxy( 1056 | proto: Socks5ServerProtocol, 1057 | addr: &TargetAddr, 1058 | request_timeout_s: u64, 1059 | nodelay: bool, 1060 | ) -> Result { 1061 | let addr = try_notify!( 1062 | proto, 1063 | addr.to_socket_addrs() 1064 | .err_when("converting to socket addr") 1065 | .and_then(|mut addrs| addrs.next().ok_or(SocksServerError::Bug("no socket addrs"))) 1066 | ); 1067 | 1068 | // TCP connect with timeout, to avoid memory leak for connection that takes forever 1069 | let outbound = match tcp_connect_with_timeout(addr, request_timeout_s).await { 1070 | Ok(stream) => stream, 1071 | Err(err) => { 1072 | proto.reply_error(&err.to_reply_error()).await?; 1073 | return Err(err.into()); 1074 | } 1075 | }; 1076 | 1077 | // Disable Nagle's algorithm if config specifies to do so. 1078 | try_notify!( 1079 | proto, 1080 | outbound.set_nodelay(nodelay).err_when("setting nodelay") 1081 | ); 1082 | 1083 | debug!("Connected to remote destination"); 1084 | 1085 | let mut inner = proto 1086 | .reply_success(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0)) 1087 | .await?; 1088 | 1089 | transfer(&mut inner, outbound).await; 1090 | Ok(inner) 1091 | } 1092 | 1093 | fn udp_bind_random_port(addr: Option) -> io::Result { 1094 | if let Some(addr) = addr { 1095 | let sock_addr = SocketAddr::new(addr, 0); 1096 | let socket = Socket::new(Domain::for_address(sock_addr), Type::DGRAM, None)?; 1097 | socket.bind(&sock_addr.into())?; 1098 | Ok(socket) 1099 | } else { 1100 | const V4_UNSPEC: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); 1101 | const V6_UNSPEC: SocketAddr = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0); 1102 | Socket::new(Domain::IPV6, Type::DGRAM, None) 1103 | .and_then(|socket| socket.set_only_v6(false).map(|_| socket)) 1104 | .and_then(|socket| socket.bind(&V6_UNSPEC.into()).map(|_| socket)) 1105 | .or_else(|_| { 1106 | Socket::new(Domain::IPV4, Type::DGRAM, None) 1107 | .and_then(|socket| socket.bind(&V4_UNSPEC.into()).map(|_| socket)) 1108 | }) 1109 | } 1110 | .and_then(|socket| socket.set_nonblocking(true).map(|_| socket)) 1111 | } 1112 | 1113 | /// Handle the associate command by running a UDP proxy until the connection is done. 1114 | pub async fn run_udp_proxy( 1115 | proto: Socks5ServerProtocol, 1116 | addr: &TargetAddr, 1117 | peer_bind_ip: Option, 1118 | reply_ip: IpAddr, 1119 | outbound_bind_ip: Option, 1120 | ) -> Result { 1121 | run_udp_proxy_custom( 1122 | proto, 1123 | addr, 1124 | peer_bind_ip, 1125 | reply_ip, 1126 | move |inbound| async move { 1127 | let outbound = 1128 | udp_bind_random_port(outbound_bind_ip).err_when("binding outbound udp socket")?; 1129 | 1130 | transfer_udp(inbound, outbound).await 1131 | }, 1132 | ) 1133 | .await 1134 | } 1135 | 1136 | /// Handle the associate command by running a UDP proxy until the connection is done. 1137 | /// 1138 | /// This version allows passing in a custom transfer function while reusing the initialization code. 1139 | pub async fn run_udp_proxy_custom( 1140 | proto: Socks5ServerProtocol, 1141 | _addr: &TargetAddr, 1142 | peer_bind_ip: Option, 1143 | reply_ip: IpAddr, 1144 | transfer: F, 1145 | ) -> Result 1146 | where 1147 | T: AsyncRead + AsyncWrite + Unpin, 1148 | F: FnOnce(Socket) -> R, 1149 | R: Future>, 1150 | { 1151 | // The DST.ADDR and DST.PORT fields contain the address and port that 1152 | // the client expects to use to send UDP datagrams on for the 1153 | // association. The server MAY use this information to limit access 1154 | // to the association. 1155 | // @see Page 6, https://datatracker.ietf.org/doc/html/rfc1928. 1156 | // 1157 | // We do NOT limit the access from the client currently in this implementation. 1158 | 1159 | // By default, listen on a UDP6 socket, so that the client can connect 1160 | // to it with either IPv4 or IPv6. 1161 | let peer_sock = try_notify!( 1162 | proto, 1163 | udp_bind_random_port(peer_bind_ip).err_when("binding client udp socket") 1164 | ); 1165 | 1166 | let peer_addr = try_notify!( 1167 | proto, 1168 | peer_sock.local_addr().err_when("getting peer's local addr") 1169 | ); 1170 | 1171 | let reply_port = peer_addr 1172 | .as_socket() 1173 | .ok_or(SocksServerError::Bug("addr not IP"))? 1174 | .port(); 1175 | 1176 | // Respect the pre-populated reply IP address. 1177 | let mut inner = proto 1178 | .reply_success(SocketAddr::new(reply_ip, reply_port)) 1179 | .await?; 1180 | 1181 | let udp_fut = transfer(peer_sock); 1182 | let tcp_fut = wait_on_tcp(&mut inner); 1183 | match try_join!(udp_fut, tcp_fut) { 1184 | Ok(_) => warn!("unreachable"), 1185 | Err(SocksServerError::EOF) => debug!("EOF on controlling TCP stream, closed UDP proxy"), 1186 | Err(err) => warn!("while UDP proxying: {err}"), 1187 | } 1188 | Ok(inner) 1189 | } 1190 | 1191 | /// Wait until a TCP stream (that's not supposed to receive anything) closes. 1192 | /// 1193 | /// This is intended for cancelling the `transfer_udp` task. 1194 | pub async fn wait_on_tcp(stream: &mut I) -> Result<(), SocksServerError> 1195 | where 1196 | I: AsyncRead + Unpin, 1197 | { 1198 | let mut buf = [0; 1]; 1199 | match stream.read(&mut buf).await { 1200 | Ok(0) => Err(SocksServerError::EOF), 1201 | Ok(_) => Err(SocksServerError::UnexpectedUdpControlGarbage(buf[0])), 1202 | Err(err) => Err(err).err_when("waiting on UDP control stream"), 1203 | } 1204 | } 1205 | 1206 | /// Run a bidirectional proxy between two streams. 1207 | /// Using 2 different generators, because they could be different structs with same traits. 1208 | pub async fn transfer(mut inbound: I, mut outbound: O) 1209 | where 1210 | I: AsyncRead + AsyncWrite + Unpin, 1211 | O: AsyncRead + AsyncWrite + Unpin, 1212 | { 1213 | match tokio::io::copy_bidirectional(&mut inbound, &mut outbound).await { 1214 | Ok(res) => info!("transfer closed ({}, {})", res.0, res.1), 1215 | Err(err) => error!("transfer error: {:?}", err), 1216 | }; 1217 | } 1218 | 1219 | async fn handle_udp_request( 1220 | inbound: &UdpSocket, 1221 | outbound: &UdpSocket, 1222 | outbound_v6: bool, 1223 | buf: &mut [u8], 1224 | ) -> Result<(), SocksServerError> { 1225 | let (size, client_addr) = inbound 1226 | .recv_from(buf) 1227 | .await 1228 | .err_when("udp receiving from")?; 1229 | debug!("Server recieve udp from {}", client_addr); 1230 | inbound 1231 | .connect(client_addr) 1232 | .await 1233 | .err_when("connecting udp inbound")?; 1234 | 1235 | let (frag, target_addr, data) = parse_udp_request(&buf[..size]).await?; 1236 | 1237 | if frag != 0 { 1238 | debug!("Discard UDP frag packets sliently."); 1239 | return Ok(()); 1240 | } 1241 | 1242 | debug!("Server forward to packet to {}", target_addr); 1243 | let mut target_addr = target_addr 1244 | .resolve_dns() 1245 | .await? 1246 | .to_socket_addrs() 1247 | .err_when("udp target to socket addrs")? 1248 | .next() 1249 | .ok_or(SocksServerError::Bug("no socket addrs"))?; 1250 | 1251 | if outbound_v6 { 1252 | target_addr.set_ip(match target_addr.ip() { 1253 | std::net::IpAddr::V4(v4) => std::net::IpAddr::V6(v4.to_ipv6_mapped()), 1254 | v6 @ std::net::IpAddr::V6(_) => v6, 1255 | }); 1256 | } 1257 | outbound 1258 | .send_to(data, target_addr) 1259 | .await 1260 | .err_when("udp sending to")?; 1261 | Ok(()) 1262 | } 1263 | 1264 | async fn handle_udp_requests( 1265 | inbound: &UdpSocket, 1266 | outbound: &UdpSocket, 1267 | ) -> Result<(), SocksServerError> { 1268 | let mut buf = vec![0u8; 8192]; 1269 | let outbound_v6 = outbound 1270 | .local_addr() 1271 | .err_when("udp outbound local addr")? 1272 | .is_ipv6(); 1273 | loop { 1274 | match handle_udp_request(inbound, outbound, outbound_v6, &mut buf).await { 1275 | Ok(_) => trace!("handled udp response"), 1276 | Err(err) => debug!("error in handling udp response: {err}"), 1277 | } 1278 | } 1279 | } 1280 | 1281 | async fn handle_udp_response( 1282 | inbound: &UdpSocket, 1283 | outbound: &UdpSocket, 1284 | buf: &mut [u8], 1285 | ) -> Result<(), SocksServerError> { 1286 | let (size, mut remote_addr) = outbound 1287 | .recv_from(buf) 1288 | .await 1289 | .err_when("udp receiving from")?; 1290 | debug!("Recieve packet from {}", remote_addr); 1291 | 1292 | // Clients don't tend to expect v6-mapped addresses when they connect to v4 ones 1293 | if let std::net::IpAddr::V6(v6) = remote_addr.ip() { 1294 | if let Some(v4) = v6.to_ipv4_mapped() { 1295 | remote_addr.set_ip(std::net::IpAddr::V4(v4)); 1296 | } 1297 | } 1298 | 1299 | let mut data = new_udp_header(remote_addr)?; 1300 | data.extend_from_slice(&buf[..size]); 1301 | inbound.send(&data).await.err_when("udp sending")?; 1302 | 1303 | Ok(()) 1304 | } 1305 | 1306 | async fn handle_udp_responses( 1307 | inbound: &UdpSocket, 1308 | outbound: &UdpSocket, 1309 | ) -> Result<(), SocksServerError> { 1310 | let mut buf = vec![0u8; 8192]; 1311 | loop { 1312 | match handle_udp_response(inbound, outbound, &mut buf).await { 1313 | Ok(_) => trace!("handled udp response"), 1314 | Err(err) => debug!("error in handling udp response: {err}"), 1315 | } 1316 | } 1317 | } 1318 | 1319 | /// Run a bidirectional UDP SOCKS proxy for a given pair of inbound (SOCKS client) and outbound sockets. 1320 | pub async fn transfer_udp(inbound: Socket, outbound: Socket) -> Result<(), SocksServerError> { 1321 | let inbound = UdpSocket::from_std(inbound.into()).err_when("wrapping inbound socket")?; 1322 | let outbound = UdpSocket::from_std(outbound.into()).err_when("wrapping outbound socket")?; 1323 | let req_fut = handle_udp_requests(&inbound, &outbound); 1324 | let res_fut = handle_udp_responses(&inbound, &outbound); 1325 | try_join!(req_fut, res_fut).map(|_| ()) 1326 | } 1327 | 1328 | // Fixes the issue "cannot borrow data in dereference of `Pin<&mut >` as mutable" 1329 | // 1330 | // cf. https://users.rust-lang.org/t/take-in-impl-future-cannot-borrow-data-in-a-dereference-of-pin/52042 1331 | #[allow(deprecated)] 1332 | impl Unpin for Socks5Socket where T: AsyncRead + AsyncWrite + Unpin {} 1333 | 1334 | /// Allow us to read directly from the struct 1335 | #[allow(deprecated)] 1336 | impl AsyncRead for Socks5Socket 1337 | where 1338 | T: AsyncRead + AsyncWrite + Unpin, 1339 | { 1340 | fn poll_read( 1341 | mut self: Pin<&mut Self>, 1342 | context: &mut std::task::Context, 1343 | buf: &mut tokio::io::ReadBuf<'_>, 1344 | ) -> Poll> { 1345 | Pin::new(&mut self.inner).poll_read(context, buf) 1346 | } 1347 | } 1348 | 1349 | /// Allow us to write directly into the struct 1350 | #[allow(deprecated)] 1351 | impl AsyncWrite for Socks5Socket 1352 | where 1353 | T: AsyncRead + AsyncWrite + Unpin, 1354 | { 1355 | fn poll_write( 1356 | mut self: Pin<&mut Self>, 1357 | context: &mut std::task::Context, 1358 | buf: &[u8], 1359 | ) -> Poll> { 1360 | Pin::new(&mut self.inner).poll_write(context, buf) 1361 | } 1362 | 1363 | fn poll_flush( 1364 | mut self: Pin<&mut Self>, 1365 | context: &mut std::task::Context, 1366 | ) -> Poll> { 1367 | Pin::new(&mut self.inner).poll_flush(context) 1368 | } 1369 | 1370 | fn poll_shutdown( 1371 | mut self: Pin<&mut Self>, 1372 | context: &mut std::task::Context, 1373 | ) -> Poll> { 1374 | Pin::new(&mut self.inner).poll_shutdown(context) 1375 | } 1376 | } 1377 | 1378 | /// Generate reply code according to the RFC. 1379 | fn new_reply(error: &ReplyError, sock_addr: SocketAddr) -> Vec { 1380 | let (addr_type, mut ip_oct, mut port) = match sock_addr { 1381 | SocketAddr::V4(sock) => ( 1382 | consts::SOCKS5_ADDR_TYPE_IPV4, 1383 | sock.ip().octets().to_vec(), 1384 | sock.port().to_be_bytes().to_vec(), 1385 | ), 1386 | SocketAddr::V6(sock) => ( 1387 | consts::SOCKS5_ADDR_TYPE_IPV6, 1388 | sock.ip().octets().to_vec(), 1389 | sock.port().to_be_bytes().to_vec(), 1390 | ), 1391 | }; 1392 | 1393 | let mut reply = vec![ 1394 | consts::SOCKS5_VERSION, 1395 | error.as_u8(), // transform the error into byte code 1396 | 0x00, // reserved 1397 | addr_type, // address type (ipv4, v6, domain) 1398 | ]; 1399 | reply.append(&mut ip_oct); 1400 | reply.append(&mut port); 1401 | 1402 | reply 1403 | } 1404 | 1405 | #[cfg(test)] 1406 | #[allow(deprecated)] 1407 | mod test { 1408 | use crate::server::Socks5Server; 1409 | use tokio_test::block_on; 1410 | 1411 | use super::AcceptAuthentication; 1412 | 1413 | #[test] 1414 | fn test_bind() { 1415 | let f = async { 1416 | let _server = Socks5Server::::bind("127.0.0.1:1080") 1417 | .await 1418 | .unwrap(); 1419 | }; 1420 | 1421 | block_on(f); 1422 | } 1423 | } 1424 | -------------------------------------------------------------------------------- /src/socks4/client.rs: -------------------------------------------------------------------------------- 1 | #[forbid(unsafe_code)] 2 | use crate::read_exact; 3 | use crate::socks4::{consts, ReplyError, Socks4Command}; 4 | use crate::util::target_addr::{TargetAddr, ToTargetAddr}; 5 | use crate::{Result, SocksError, SocksError::ReplySocks4Error}; 6 | use anyhow::Context; 7 | use std::io; 8 | use std::net::SocketAddr; 9 | use std::net::ToSocketAddrs; 10 | use std::pin::Pin; 11 | use std::task::Poll; 12 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 13 | use tokio::net::TcpStream; 14 | 15 | const MAX_ADDR_LEN: usize = 260; 16 | 17 | /// A SOCKS4 client. 18 | /// `Socks4Stream` implements [`AsyncRead`] and [`AsyncWrite`]. 19 | #[derive(Debug)] 20 | pub struct Socks4Stream { 21 | socket: S, 22 | target_addr: Option, 23 | } 24 | 25 | impl Socks4Stream 26 | where 27 | S: AsyncRead + AsyncWrite + Unpin, 28 | { 29 | /// Possibility to use a stream already created rather than 30 | /// creating a whole new `TcpStream::connect()`. 31 | pub fn use_stream(socket: S) -> Result { 32 | let stream = Socks4Stream { 33 | socket, 34 | target_addr: None, 35 | }; 36 | Ok(stream) 37 | } 38 | 39 | /// https://www.openssh.com/txt/socks4.protocol 40 | /// https://www.openssh.com/txt/socks4a.protocol 41 | /// 42 | /// 1) CONNECT 43 | /// 44 | /// +----+----+----+----+----+----+----+----+----+----+....+----+ 45 | /// | VN | CD | DSTPORT | DSTIP | USERID |NULL| 46 | /// +----+----+----+----+----+----+----+----+----+----+....+----+ 47 | /// #of bytes 1 1 2 4 variable 1 48 | /// 49 | /// VN is the SOCKS protocol version number and should be 4. CD is the 50 | /// SOCKS command code and should be 1 for CONNECT request. NULL is a byte 51 | /// of all zero bits. 52 | /// 53 | /// The SOCKS server checks to see whether such a request should be granted 54 | /// based on any combination of source IP address, destination IP address, 55 | /// destination port number, the userid, and information it may obtain by 56 | /// consulting IDENT, cf. RFC 1413. If the request is granted, the SOCKS 57 | /// server makes a connection to the specified port of the destination host. 58 | /// A reply packet is sent to the client when this connection is established, 59 | /// or when the request is rejected or the operation fails. 60 | /// 61 | /// Response: 62 | /// 63 | /// +----+----+----+----+----+----+----+----+ 64 | /// | VN | CD | DSTPORT | DSTIP | 65 | /// +----+----+----+----+----+----+----+----+ 66 | /// #of bytes 1 1 2 4 67 | /// 68 | /// VN is the version of the reply code and should be 0. CD is the result 69 | /// code with one of the following values: 70 | /// 71 | /// 90: request granted 72 | /// 91: request rejected or failed 73 | /// 92: request rejected because SOCKS server cannot connect to 74 | /// identd on the client 75 | /// 93: request rejected because the client program and identd 76 | /// report different user-ids 77 | /// 78 | pub async fn request( 79 | &mut self, 80 | cmd: Socks4Command, 81 | target_addr: TargetAddr, 82 | resolve_locally: bool, 83 | ) -> Result<()> { 84 | let resolved = if target_addr.is_domain() && resolve_locally { 85 | target_addr.resolve_dns().await? 86 | } else { 87 | target_addr 88 | }; 89 | self.target_addr = Some(resolved); 90 | self.send_command_request(&cmd).await?; 91 | self.read_command_request().await?; 92 | 93 | Ok(()) 94 | } 95 | 96 | async fn send_command_request(&mut self, cmd: &Socks4Command) -> Result<()> { 97 | let mut packet = [0u8; MAX_ADDR_LEN]; 98 | packet[0] = consts::SOCKS4_VERSION; 99 | packet[1] = cmd.as_u8(); 100 | 101 | match &self.target_addr { 102 | Some(TargetAddr::Ip(SocketAddr::V4(addr))) => { 103 | packet[2] = (addr.port() >> 8) as u8; 104 | packet[3] = addr.port() as u8; 105 | packet[4..8].copy_from_slice(&(addr.ip()).octets()); 106 | Ok(()) 107 | } 108 | Some(TargetAddr::Ip(SocketAddr::V6(addr))) => { 109 | error!("IPv6 are not supported: {:?}", addr); 110 | Err(ReplySocks4Error(ReplyError::AddressTypeNotSupported)) 111 | } 112 | Some(TargetAddr::Domain(domain, port)) => { 113 | packet[2] = (port >> 8) as u8; 114 | packet[3] = *port as u8; 115 | packet[4..8].copy_from_slice(&[0, 0, 0, 1]); 116 | let domain_bytes = domain.as_bytes(); 117 | let offset = 8 + domain_bytes.len(); 118 | packet[8..offset].copy_from_slice(domain_bytes); 119 | Ok(()) 120 | } 121 | _ => { 122 | panic!("Unreachable case"); 123 | } 124 | }?; 125 | self.socket.write_all(&packet).await?; 126 | Ok(()) 127 | } 128 | 129 | #[rustfmt::skip] 130 | async fn read_command_request(&mut self) -> Result<()> { 131 | let [_, cd] = read_exact!(self.socket, [0u8; 2])?; 132 | let reply = ReplyError::from_u8(cd); 133 | match reply { 134 | ReplyError::Succeeded => Ok(()), 135 | _ => Err(SocksError::ReplySocks4Error(reply)) 136 | } 137 | } 138 | 139 | pub fn get_socket(self) -> S { 140 | self.socket 141 | } 142 | 143 | pub fn get_socket_ref(&self) -> &S { 144 | &self.socket 145 | } 146 | 147 | pub fn get_socket_mut(&mut self) -> &mut S { 148 | &mut self.socket 149 | } 150 | } 151 | 152 | /// Api if you want to use TcpStream to create a new connection to the SOCKS4 server. 153 | impl Socks4Stream { 154 | /// Connects to a target server through a SOCKS4 proxy. 155 | pub async fn connect( 156 | socks_server: T, 157 | target_addr: String, 158 | target_port: u16, 159 | resolve_locally: bool, 160 | ) -> Result 161 | where 162 | T: ToSocketAddrs, 163 | { 164 | Self::connect_raw( 165 | Socks4Command::Connect, 166 | socks_server, 167 | target_addr, 168 | target_port, 169 | resolve_locally, 170 | ) 171 | .await 172 | } 173 | 174 | /// Process clients SOCKS requests 175 | /// This is the entry point where a whole request is processed. 176 | pub async fn connect_raw( 177 | cmd: Socks4Command, 178 | socks_server: T, 179 | target_addr: String, 180 | target_port: u16, 181 | resolve_locally: bool, 182 | ) -> Result 183 | where 184 | T: ToSocketAddrs, 185 | { 186 | let socket = TcpStream::connect( 187 | socks_server 188 | .to_socket_addrs()? 189 | .next() 190 | .context("unreachable")?, 191 | ) 192 | .await?; 193 | info!("Connected @ {}", &socket.peer_addr()?); 194 | 195 | // Specify the target, here domain name, dns will be resolved on the server side 196 | let target_addr = (target_addr.as_str(), target_port) 197 | .to_target_addr() 198 | .context("Can't convert address to TargetAddr format")?; 199 | 200 | // upgrade the TcpStream to Socks4Stream 201 | let mut socks_stream = Self::use_stream(socket)?; 202 | socks_stream 203 | .request(cmd, target_addr, resolve_locally) 204 | .await?; 205 | 206 | Ok(socks_stream) 207 | } 208 | } 209 | 210 | /// Allow us to read directly from the struct 211 | impl AsyncRead for Socks4Stream 212 | where 213 | S: AsyncRead + AsyncWrite + Unpin, 214 | { 215 | fn poll_read( 216 | mut self: Pin<&mut Self>, 217 | context: &mut std::task::Context, 218 | buf: &mut tokio::io::ReadBuf<'_>, 219 | ) -> Poll> { 220 | Pin::new(&mut self.socket).poll_read(context, buf) 221 | } 222 | } 223 | 224 | /// Allow us to write directly into the struct 225 | impl AsyncWrite for Socks4Stream 226 | where 227 | S: AsyncRead + AsyncWrite + Unpin, 228 | { 229 | fn poll_write( 230 | mut self: Pin<&mut Self>, 231 | context: &mut std::task::Context, 232 | buf: &[u8], 233 | ) -> Poll> { 234 | Pin::new(&mut self.socket).poll_write(context, buf) 235 | } 236 | 237 | fn poll_flush( 238 | mut self: Pin<&mut Self>, 239 | context: &mut std::task::Context, 240 | ) -> Poll> { 241 | Pin::new(&mut self.socket).poll_flush(context) 242 | } 243 | 244 | fn poll_shutdown( 245 | mut self: Pin<&mut Self>, 246 | context: &mut std::task::Context, 247 | ) -> Poll> { 248 | Pin::new(&mut self.socket).poll_shutdown(context) 249 | } 250 | } 251 | 252 | #[cfg(test)] 253 | mod tests { 254 | use super::*; 255 | 256 | fn get_domain() -> String { 257 | "www.google.com".to_string() 258 | } 259 | 260 | async fn get_humans_txt(socks: &mut Socks4Stream) -> Option { 261 | let headers = format!( 262 | "GET /humans.txt HTTP/1.1\r\n\ 263 | Host: {}\r\n\ 264 | User-Agent: fast-socks5/0.1.0\r\n\ 265 | Accept: */*\r\n\r\n", 266 | get_domain() 267 | ); 268 | 269 | socks 270 | .write_all(headers.as_bytes()) 271 | .await 272 | .expect("should successfully write"); 273 | 274 | let response = &mut [0u8; 2048]; 275 | socks 276 | .read(response) 277 | .await 278 | .expect("should successfully read"); 279 | 280 | // sometimes google returns body on second request 281 | if response[0] == 0 { 282 | response.copy_from_slice(&[0u8; 2048]); 283 | socks 284 | .read(response) 285 | .await 286 | .expect("should successfully read"); 287 | } 288 | 289 | let response_str = String::from_utf8_lossy(response); 290 | let response_body = response_str 291 | .split("\n") 292 | .into_iter() 293 | .filter(|x| x.starts_with("Google")) 294 | .last() 295 | .map(|x| x.to_string()); 296 | 297 | response_body 298 | } 299 | 300 | fn assert_response_body(response_body: &String) { 301 | let expected = 302 | "Google is built by a large team of engineers, designers, researchers, robots, \ 303 | and others in many different sites across the globe. It is updated continuously, \ 304 | and built with more tools and technologies than we can shake a stick at. If you'd \ 305 | like to help us out, see careers.google.com."; 306 | assert_eq!(expected, response_body); 307 | } 308 | 309 | #[tokio::test] 310 | pub async fn test_use_stream() { 311 | // TODO: replace with local socks4 server 312 | // it requires implementation 313 | let tcp = TcpStream::connect("217.17.56.160:4145") 314 | .await 315 | .expect("should connect to remote"); 316 | 317 | let mut socks = Socks4Stream::use_stream(tcp).expect("should wrap to socks stream"); 318 | 319 | socks 320 | .request( 321 | Socks4Command::Connect, 322 | TargetAddr::Domain(get_domain(), 80), 323 | true, 324 | ) 325 | .await 326 | .expect("should send connect successfully"); 327 | 328 | let response_body = get_humans_txt(&mut socks) 329 | .await 330 | .expect("should have response_body"); 331 | 332 | assert_response_body(&response_body); 333 | } 334 | 335 | #[tokio::test] 336 | pub async fn test_use_stream_local_resolve() { 337 | let mut socks = Socks4Stream::connect("217.17.56.160:4145", get_domain(), 80, true) 338 | .await 339 | .expect("should connect successfully to socks4 server"); 340 | 341 | let response_body = get_humans_txt(&mut socks) 342 | .await 343 | .expect("should have response_body"); 344 | 345 | assert_response_body(&response_body); 346 | } 347 | 348 | // Need to find socks4a supporting proxy or implement 349 | // custom server and test using it 350 | // 351 | // #[tokio::test] 352 | // pub async fn test_use_stream_remote_resolve() { 353 | // let mut socks = Socks4Stream::connect("217.17.56.160:4145", get_domain(), 80, false) 354 | // .await 355 | // .expect("should connect successfully to socks4 server"); 356 | // 357 | // let response_body = get_humans_txt(&mut socks) 358 | // .await 359 | // .expect("should have response_body"); 360 | // 361 | // assert_response_body(&response_body); 362 | // } 363 | } 364 | -------------------------------------------------------------------------------- /src/socks4/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | 3 | use thiserror::Error; 4 | 5 | #[rustfmt::skip] 6 | pub mod consts { 7 | pub const SOCKS4_VERSION: u8 = 0x04; 8 | 9 | pub const SOCKS4_CMD_CONNECT: u8 = 0x01; 10 | pub const SOCKS4_CMD_BIND: u8 = 0x02; 11 | 12 | pub const SOCKS4_REPLY_SUCCEEDED: u8 = 0x5a; 13 | pub const SOCKS4_REPLY_FAILED: u8 = 0x5b; 14 | pub const SOCKS4_REPLY_HOST_UNREACHABLE: u8 = 0x5c; 15 | pub const SOCKS4_REPLY_INVALID_USER: u8 = 0x5d; 16 | } 17 | 18 | /// SOCKS4 reply code 19 | #[derive(Error, Debug, Copy, Clone)] 20 | pub enum ReplyError { 21 | #[error("Succeeded")] 22 | Succeeded, 23 | #[error("General failure")] 24 | GeneralFailure, 25 | #[error("Host unreachable")] 26 | HostUnreachable, 27 | #[error("Address type not supported")] 28 | AddressTypeNotSupported, 29 | #[error("Invalid user")] 30 | InvalidUser, 31 | 32 | #[error("Unknown response")] 33 | UnknownResponse(u8), 34 | } 35 | 36 | #[derive(Debug, PartialEq)] 37 | pub enum Socks4Command { 38 | Connect, 39 | Bind, 40 | } 41 | 42 | #[allow(dead_code)] 43 | impl Socks4Command { 44 | #[inline] 45 | #[rustfmt::skip] 46 | pub fn as_u8(&self) -> u8 { 47 | match self { 48 | Socks4Command::Connect => consts::SOCKS4_CMD_CONNECT, 49 | Socks4Command::Bind => consts::SOCKS4_CMD_BIND, 50 | } 51 | } 52 | 53 | #[inline] 54 | #[rustfmt::skip] 55 | pub fn from_u8(code: u8) -> Option { 56 | match code { 57 | consts::SOCKS4_CMD_CONNECT => Some(Socks4Command::Connect), 58 | consts::SOCKS4_CMD_BIND => Some(Socks4Command::Bind), 59 | _ => None, 60 | } 61 | } 62 | } 63 | 64 | impl ReplyError { 65 | #[inline] 66 | #[rustfmt::skip] 67 | pub fn as_u8(self) -> u8 { 68 | match self { 69 | ReplyError::Succeeded => consts::SOCKS4_REPLY_SUCCEEDED, 70 | ReplyError::GeneralFailure => consts::SOCKS4_REPLY_FAILED, 71 | ReplyError::HostUnreachable => consts::SOCKS4_REPLY_HOST_UNREACHABLE, 72 | ReplyError::InvalidUser => consts::SOCKS4_REPLY_INVALID_USER, 73 | reply => panic!("Unsupported ReplyStatus: {:?}", reply) 74 | } 75 | } 76 | 77 | #[inline] 78 | #[rustfmt::skip] 79 | pub fn from_u8(code: u8) -> ReplyError { 80 | match code { 81 | consts::SOCKS4_REPLY_SUCCEEDED => ReplyError::Succeeded, 82 | consts::SOCKS4_REPLY_FAILED => ReplyError::GeneralFailure, 83 | consts::SOCKS4_REPLY_HOST_UNREACHABLE => ReplyError::HostUnreachable, 84 | consts::SOCKS4_REPLY_INVALID_USER => ReplyError::InvalidUser, 85 | _ => ReplyError::UnknownResponse(code), 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod stream; 2 | pub mod target_addr; 3 | -------------------------------------------------------------------------------- /src/util/stream.rs: -------------------------------------------------------------------------------- 1 | use crate::ReplyError; 2 | use std::io; 3 | use std::time::Duration; 4 | use tokio::io::ErrorKind as IOErrorKind; 5 | use tokio::net::{TcpStream, ToSocketAddrs}; 6 | use tokio::time::timeout; 7 | 8 | /// Easy to destructure bytes buffers by naming each fields: 9 | /// 10 | /// # Examples (before) 11 | /// 12 | /// ```ignore 13 | /// let mut buf = [0u8; 2]; 14 | /// stream.read_exact(&mut buf).await?; 15 | /// let [version, method_len] = buf; 16 | /// 17 | /// assert_eq!(version, 0x05); 18 | /// ``` 19 | /// 20 | /// # Examples (after) 21 | /// 22 | /// ```ignore 23 | /// let [version, method_len] = read_exact!(stream, [0u8; 2]); 24 | /// 25 | /// assert_eq!(version, 0x05); 26 | /// ``` 27 | #[macro_export] 28 | macro_rules! read_exact { 29 | ($stream: expr, $array: expr) => {{ 30 | let mut x = $array; 31 | // $stream 32 | // .read_exact(&mut x) 33 | // .await 34 | // .map_err(|_| io_err("lol"))?; 35 | $stream.read_exact(&mut x).await.map(|_| x) 36 | }}; 37 | } 38 | 39 | #[macro_export] 40 | macro_rules! ready { 41 | ($e:expr $(,)?) => { 42 | match $e { 43 | std::task::Poll::Ready(t) => t, 44 | std::task::Poll::Pending => return std::task::Poll::Pending, 45 | } 46 | }; 47 | } 48 | 49 | #[derive(thiserror::Error, Debug)] 50 | pub enum ConnectError { 51 | #[error("Connection timed out")] 52 | ConnectionTimeout, 53 | #[error("Connection refused: {0}")] 54 | ConnectionRefused(#[source] io::Error), 55 | #[error("Connection aborted: {0}")] 56 | ConnectionAborted(#[source] io::Error), 57 | #[error("Connection reset: {0}")] 58 | ConnectionReset(#[source] io::Error), 59 | #[error("Not connected: {0}")] 60 | NotConnected(#[source] io::Error), 61 | #[error("Other i/o error: {0}")] 62 | Other(#[source] io::Error), 63 | } 64 | 65 | impl ConnectError { 66 | pub fn to_reply_error(&self) -> ReplyError { 67 | match self { 68 | ConnectError::ConnectionTimeout => ReplyError::ConnectionTimeout, 69 | ConnectError::ConnectionRefused(_) => ReplyError::ConnectionRefused, 70 | ConnectError::ConnectionAborted(_) | ConnectError::ConnectionReset(_) => { 71 | ReplyError::ConnectionNotAllowed 72 | } 73 | ConnectError::NotConnected(_) => ReplyError::NetworkUnreachable, 74 | ConnectError::Other(_) => ReplyError::GeneralFailure, 75 | } 76 | } 77 | } 78 | 79 | pub async fn tcp_connect_with_timeout( 80 | addr: T, 81 | request_timeout_s: u64, 82 | ) -> Result 83 | where 84 | T: ToSocketAddrs, 85 | { 86 | let fut = tcp_connect(addr); 87 | match timeout(Duration::from_secs(request_timeout_s), fut).await { 88 | Ok(result) => result, 89 | Err(_) => Err(ConnectError::ConnectionTimeout), 90 | } 91 | } 92 | 93 | pub async fn tcp_connect(addr: T) -> Result 94 | where 95 | T: ToSocketAddrs, 96 | { 97 | match TcpStream::connect(addr).await { 98 | Ok(o) => Ok(o), 99 | Err(e) => match e.kind() { 100 | IOErrorKind::ConnectionRefused => Err(ConnectError::ConnectionRefused(e)), 101 | IOErrorKind::ConnectionAborted => Err(ConnectError::ConnectionAborted(e)), 102 | IOErrorKind::ConnectionReset => Err(ConnectError::ConnectionReset(e)), 103 | IOErrorKind::NotConnected => Err(ConnectError::NotConnected(e)), 104 | _ => Err(ConnectError::Other(e)), 105 | }, 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/util/target_addr.rs: -------------------------------------------------------------------------------- 1 | use crate::consts; 2 | use crate::consts::SOCKS5_ADDR_TYPE_IPV4; 3 | use crate::read_exact; 4 | use crate::ReplyError; 5 | use std::fmt; 6 | use std::io; 7 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; 8 | use std::vec::IntoIter; 9 | use tokio::io::{AsyncRead, AsyncReadExt}; 10 | use tokio::net::lookup_host; 11 | 12 | /// SOCKS5 reply code 13 | #[derive(thiserror::Error, Debug)] 14 | pub enum AddrError { 15 | #[error("DNS Resolution failed: {0}")] 16 | DNSResolutionFailed(#[source] io::Error), 17 | #[error("DNS returned no appropriate records")] 18 | NoDNSRecords, 19 | #[error("Domain length {0} exceeded maximum")] 20 | DomainLenTooLong(usize), 21 | #[error("Can't read IPv4: {0}")] 22 | IPv4Unreadable(#[source] io::Error), 23 | #[error("Can't read IPv6: {0}")] 24 | IPv6Unreadable(#[source] io::Error), 25 | #[error("Can't read port number: {0}")] 26 | PortNumberUnreadable(#[source] io::Error), 27 | #[error("Can't read domain len: {0}")] 28 | DomainLenUnreadable(#[source] io::Error), 29 | #[error("Can't read domain content: {0}")] 30 | DomainContentUnreadable(#[source] io::Error), 31 | #[error("Can't convert address: {0}")] 32 | AddrConversionFailed(#[source] io::Error), 33 | #[error("Malformed UTF-8")] 34 | Utf8(#[source] std::string::FromUtf8Error), 35 | #[error("Unknown address type")] 36 | IncorrectAddressType, 37 | } 38 | 39 | impl AddrError { 40 | pub fn to_reply_error(&self) -> ReplyError { 41 | match self { 42 | AddrError::IncorrectAddressType => ReplyError::AddressTypeNotSupported, 43 | _ => ReplyError::ConnectionRefused, 44 | } 45 | } 46 | } 47 | 48 | /// A description of a connection target. 49 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 50 | pub enum TargetAddr { 51 | /// Connect to an IP address. 52 | Ip(SocketAddr), 53 | /// Connect to a fully qualified domain name. 54 | /// 55 | /// The domain name will be passed along to the proxy server and DNS lookup 56 | /// will happen there. 57 | Domain(String, u16), 58 | } 59 | 60 | impl TargetAddr { 61 | pub async fn resolve_dns(self) -> Result { 62 | match self { 63 | TargetAddr::Ip(ip) => Ok(TargetAddr::Ip(ip)), 64 | TargetAddr::Domain(domain, port) => { 65 | debug!("Attempt to DNS resolve the domain {}...", &domain); 66 | 67 | let socket_addr = lookup_host((&domain[..], port)) 68 | .await 69 | .map_err(|err| AddrError::DNSResolutionFailed(err))? 70 | .next() 71 | .ok_or(AddrError::NoDNSRecords)?; 72 | debug!("domain name resolved to {}", socket_addr); 73 | 74 | // has been converted to an ip 75 | Ok(TargetAddr::Ip(socket_addr)) 76 | } 77 | } 78 | } 79 | 80 | pub fn is_ip(&self) -> bool { 81 | match self { 82 | TargetAddr::Ip(_) => true, 83 | _ => false, 84 | } 85 | } 86 | 87 | pub fn is_domain(&self) -> bool { 88 | !self.is_ip() 89 | } 90 | 91 | pub fn to_be_bytes(&self) -> Result, AddrError> { 92 | let mut buf = vec![]; 93 | match self { 94 | TargetAddr::Ip(SocketAddr::V4(addr)) => { 95 | debug!("TargetAddr::IpV4"); 96 | 97 | buf.extend_from_slice(&[SOCKS5_ADDR_TYPE_IPV4]); 98 | 99 | debug!("addr ip {:?}", (*addr.ip()).octets()); 100 | buf.extend_from_slice(&(addr.ip()).octets()); // ip 101 | buf.extend_from_slice(&addr.port().to_be_bytes()); // port 102 | } 103 | TargetAddr::Ip(SocketAddr::V6(addr)) => { 104 | debug!("TargetAddr::IpV6"); 105 | buf.extend_from_slice(&[consts::SOCKS5_ADDR_TYPE_IPV6]); 106 | 107 | debug!("addr ip {:?}", (*addr.ip()).octets()); 108 | buf.extend_from_slice(&(addr.ip()).octets()); // ip 109 | buf.extend_from_slice(&addr.port().to_be_bytes()); // port 110 | } 111 | TargetAddr::Domain(ref domain, port) => { 112 | debug!("TargetAddr::Domain"); 113 | if domain.len() > u8::max_value() as usize { 114 | return Err(AddrError::DomainLenTooLong(domain.len())); 115 | } 116 | buf.extend_from_slice(&[consts::SOCKS5_ADDR_TYPE_DOMAIN_NAME, domain.len() as u8]); 117 | buf.extend_from_slice(domain.as_bytes()); // domain content 118 | buf.extend_from_slice(&port.to_be_bytes()); 119 | // port content (.to_be_bytes() convert from u16 to u8 type) 120 | } 121 | } 122 | Ok(buf) 123 | } 124 | 125 | pub fn into_string_and_port(self) -> (String, u16) { 126 | match self { 127 | TargetAddr::Ip(socket_addr) => (socket_addr.ip().to_string(), socket_addr.port()), 128 | TargetAddr::Domain(domain, port) => (domain, port), 129 | } 130 | } 131 | } 132 | 133 | // async-std ToSocketAddrs doesn't supports external trait implementation 134 | // @see https://github.com/async-rs/async-std/issues/539 135 | impl std::net::ToSocketAddrs for TargetAddr { 136 | type Iter = IntoIter; 137 | 138 | fn to_socket_addrs(&self) -> io::Result> { 139 | match *self { 140 | TargetAddr::Ip(addr) => Ok(vec![addr].into_iter()), 141 | TargetAddr::Domain(_, _) => Err(io::Error::new( 142 | io::ErrorKind::Other, 143 | "Domain name has to be explicitly resolved, please use TargetAddr::resolve_dns().", 144 | )), 145 | } 146 | } 147 | } 148 | 149 | impl fmt::Display for TargetAddr { 150 | #[inline] 151 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 152 | match *self { 153 | TargetAddr::Ip(ref addr) => write!(f, "{}", addr), 154 | TargetAddr::Domain(ref addr, ref port) => write!(f, "{}:{}", addr, port), 155 | } 156 | } 157 | } 158 | 159 | /// A trait for objects that can be converted to `TargetAddr`. 160 | pub trait ToTargetAddr { 161 | /// Converts the value of `self` to a `TargetAddr`. 162 | fn to_target_addr(&self) -> io::Result; 163 | } 164 | 165 | impl<'a> ToTargetAddr for (&'a str, u16) { 166 | fn to_target_addr(&self) -> io::Result { 167 | // try to parse as an IP first 168 | if let Ok(addr) = self.0.parse::() { 169 | return (addr, self.1).to_target_addr(); 170 | } 171 | 172 | if let Ok(addr) = self.0.parse::() { 173 | return (addr, self.1).to_target_addr(); 174 | } 175 | 176 | Ok(TargetAddr::Domain(self.0.to_owned(), self.1)) 177 | } 178 | } 179 | 180 | impl ToTargetAddr for SocketAddr { 181 | fn to_target_addr(&self) -> io::Result { 182 | Ok(TargetAddr::Ip(*self)) 183 | } 184 | } 185 | 186 | impl ToTargetAddr for SocketAddrV4 { 187 | fn to_target_addr(&self) -> io::Result { 188 | SocketAddr::V4(*self).to_target_addr() 189 | } 190 | } 191 | 192 | impl ToTargetAddr for SocketAddrV6 { 193 | fn to_target_addr(&self) -> io::Result { 194 | SocketAddr::V6(*self).to_target_addr() 195 | } 196 | } 197 | 198 | impl ToTargetAddr for (IpAddr, u16) { 199 | fn to_target_addr(&self) -> io::Result { 200 | match self.0 { 201 | IpAddr::V4(ipv4_addr) => (ipv4_addr, self.1).to_target_addr(), 202 | IpAddr::V6(ipv6_addr) => (ipv6_addr, self.1).to_target_addr(), 203 | } 204 | } 205 | } 206 | 207 | impl ToTargetAddr for (Ipv4Addr, u16) { 208 | fn to_target_addr(&self) -> io::Result { 209 | SocketAddrV4::new(self.0, self.1).to_target_addr() 210 | } 211 | } 212 | 213 | impl ToTargetAddr for (Ipv6Addr, u16) { 214 | fn to_target_addr(&self) -> io::Result { 215 | SocketAddrV6::new(self.0, self.1, 0, 0).to_target_addr() 216 | } 217 | } 218 | 219 | impl ToTargetAddr for TargetAddr { 220 | fn to_target_addr(&self) -> io::Result { 221 | Ok(self.clone()) 222 | } 223 | } 224 | 225 | #[derive(Debug)] 226 | pub enum Addr { 227 | V4([u8; 4]), 228 | V6([u8; 16]), 229 | Domain(String), // Vec<[u8]> or Box<[u8]> or String ? 230 | } 231 | 232 | /// This function is used by the client & the server 233 | pub async fn read_address( 234 | stream: &mut T, 235 | atyp: u8, 236 | ) -> Result { 237 | let addr = match atyp { 238 | consts::SOCKS5_ADDR_TYPE_IPV4 => { 239 | debug!("Address type `IPv4`"); 240 | Addr::V4(read_exact!(stream, [0u8; 4]).map_err(|err| AddrError::IPv4Unreadable(err))?) 241 | } 242 | consts::SOCKS5_ADDR_TYPE_IPV6 => { 243 | debug!("Address type `IPv6`"); 244 | Addr::V6(read_exact!(stream, [0u8; 16]).map_err(|err| AddrError::IPv6Unreadable(err))?) 245 | } 246 | consts::SOCKS5_ADDR_TYPE_DOMAIN_NAME => { 247 | debug!("Address type `domain`"); 248 | let len = 249 | read_exact!(stream, [0]).map_err(|err| AddrError::DomainLenUnreadable(err))?[0]; 250 | let domain = read_exact!(stream, vec![0u8; len as usize]) 251 | .map_err(|err| AddrError::DomainContentUnreadable(err))?; 252 | // make sure the bytes are correct utf8 string 253 | let domain = String::from_utf8(domain).map_err(|err| AddrError::Utf8(err))?; 254 | 255 | Addr::Domain(domain) 256 | } 257 | _ => return Err(AddrError::IncorrectAddressType), 258 | }; 259 | 260 | // Find port number 261 | let port = read_exact!(stream, [0u8; 2]).map_err(|err| AddrError::PortNumberUnreadable(err))?; 262 | // Convert (u8 * 2) into u16 263 | let port = (port[0] as u16) << 8 | port[1] as u16; 264 | 265 | // Merge ADDRESS + PORT into a TargetAddr 266 | let addr: TargetAddr = match addr { 267 | Addr::V4([a, b, c, d]) => (Ipv4Addr::new(a, b, c, d), port) 268 | .to_target_addr() 269 | .map_err(|err| AddrError::AddrConversionFailed(err))?, 270 | Addr::V6(x) => (Ipv6Addr::from(x), port) 271 | .to_target_addr() 272 | .map_err(|err| AddrError::AddrConversionFailed(err))?, 273 | Addr::Domain(domain) => TargetAddr::Domain(domain, port), 274 | }; 275 | 276 | Ok(addr) 277 | } 278 | -------------------------------------------------------------------------------- /test_udp.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import socks 3 | import dns.message # pip install dnspython 4 | 5 | def create_dns_query(domain): 6 | # Create a DNS query message 7 | query = dns.message.make_query(domain, 'A') 8 | return query.to_wire() 9 | 10 | def parse_dns_response(response_data): 11 | # Parse the DNS response 12 | response = dns.message.from_wire(response_data) 13 | return response 14 | 15 | # Setup SOCKS connection 16 | s = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM) 17 | s.set_proxy(socks.SOCKS5, "localhost", 1337) # your SOCKS5 port 18 | 19 | try: 20 | # Send DNS query for google.com 21 | query_data = create_dns_query("google.com") 22 | s.sendto(query_data, ("8.8.8.8", 53)) # Google's DNS server 23 | 24 | # Receive response 25 | data, addr = s.recvfrom(1024) 26 | 27 | # Parse and print response 28 | response = parse_dns_response(data) 29 | print(f"Response from {addr}:") 30 | print(response) 31 | 32 | # Print A records 33 | for answer in response.answer: 34 | for item in answer.items: 35 | if item.rdtype == dns.rdatatype.A: 36 | print(f"IP Address: {item}") 37 | 38 | except Exception as e: 39 | print(f"Error: {e}") 40 | finally: 41 | s.close() 42 | -------------------------------------------------------------------------------- /tests/sock5_client_test.rs: -------------------------------------------------------------------------------- 1 | use tokio::net::{TcpListener}; 2 | use tokio::io::{self, AsyncReadExt, AsyncWriteExt}; 3 | use std::time::Duration; 4 | use tokio::time::timeout; 5 | use tokio_test::assert_ok; 6 | use fast_socks5::client::{Config, Socks5Stream}; 7 | 8 | #[tokio::test] 9 | async fn test_socks5_connection() -> io::Result<()> { 10 | let socks_server = TcpListener::bind("127.0.0.1:0").await?; 11 | let addr = socks_server.local_addr()?; 12 | 13 | tokio::spawn(async move { 14 | let (mut stream, _) = socks_server.accept().await.expect("Server accept failed"); 15 | let mut buf = [0u8; 100]; 16 | 17 | let bytes_read = stream.read(&mut buf).await.expect("Read initial handshake"); 18 | assert_eq!(&buf[..bytes_read], [0x05, 0x01, 0x00]); 19 | stream.write_all(&[0x05, 0x00]).await.expect("Write handshake response"); 20 | 21 | let bytes_read = stream.read(&mut buf).await.expect("Read request"); 22 | assert_eq!(&buf[..bytes_read], &[0x05, 0x01, 0x00, 0x03, 0x05, b't', b'e', b'.', b's', b't', 0x00, 0x50]); 23 | stream.write_all(&[0x05,0x00,0x00,0x01,0xff, 0x00,0x00,0x01,0x00,0x50]).await.expect("Write response"); 24 | 25 | let bytes_read = stream.read(&mut buf).await.expect("Read 'get' request"); 26 | assert_eq!(&buf[..bytes_read], &[b'g', b'e', b't']); 27 | stream.write_all(b"all ok").await.expect("Write 'all ok'"); 28 | stream.shutdown().await.expect("Shutdown stream"); 29 | }); 30 | 31 | let mut socks_client = assert_ok!(Socks5Stream::connect( 32 | addr, 33 | "te.st".to_string(), 34 | 80, 35 | Config::default() 36 | ).await); 37 | socks_client.write_all(b"get").await?; 38 | 39 | let mut resp = String::new(); 40 | timeout(Duration::from_secs(1), async { 41 | socks_client.read_to_string(&mut resp).await.expect("Read response"); 42 | }).await?; 43 | 44 | assert_eq!(resp, "all ok"); 45 | Ok(()) 46 | } 47 | --------------------------------------------------------------------------------