123 lines
3.7 KiB
Rust
123 lines
3.7 KiB
Rust
#[forbid(unsafe_code)]
|
|
#[macro_use]
|
|
extern crate log;
|
|
|
|
use anyhow::Context;
|
|
use fast_socks5::client::Config;
|
|
use fast_socks5::{client::Socks5Stream, Result};
|
|
use structopt::StructOpt;
|
|
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
|
|
|
/// # How to use it:
|
|
///
|
|
/// GET / of web server by IPv4 address:
|
|
/// `$ 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`
|
|
///
|
|
/// GET / of web server by IPv6 address:
|
|
/// `$ 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`
|
|
///
|
|
/// GET / of web server by domain name:
|
|
/// `$ RUST_LOG=debug cargo run --example client -- --socks-server 127.0.0.1:1337 --username admin --password password -a perdu.com -p 80`
|
|
///
|
|
#[derive(Debug, StructOpt)]
|
|
#[structopt(name = "socks5-client", about = "A simple example of a socks5-client.")]
|
|
struct Opt {
|
|
/// Socks5 server address + port. eg. `127.0.0.1:1080`
|
|
#[structopt(short, long)]
|
|
pub socks_server: String,
|
|
|
|
/// Target address server (not the socks server)
|
|
#[structopt(short = "a", long)]
|
|
pub target_addr: String,
|
|
|
|
/// Target port server (not the socks server)
|
|
#[structopt(short = "p", long)]
|
|
pub target_port: u16,
|
|
|
|
#[structopt(short, long)]
|
|
pub username: Option<String>,
|
|
|
|
#[structopt(long)]
|
|
pub password: Option<String>,
|
|
|
|
/// Don't perform the auth handshake, send directly the command request
|
|
#[structopt(short = "k", long)]
|
|
pub skip_auth: bool,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
env_logger::init();
|
|
|
|
spawn_socks_client().await
|
|
}
|
|
|
|
async fn spawn_socks_client() -> Result<()> {
|
|
let opt: Opt = Opt::from_args();
|
|
let domain = opt.target_addr.clone();
|
|
let mut socks;
|
|
let mut config = Config::default();
|
|
config.set_skip_auth(opt.skip_auth);
|
|
|
|
// Creating a SOCKS stream to the target address thru the socks server
|
|
if opt.username.is_some() {
|
|
socks = Socks5Stream::connect_with_password(
|
|
opt.socks_server,
|
|
opt.target_addr,
|
|
opt.target_port,
|
|
opt.username.unwrap(),
|
|
opt.password.expect("Please fill the password"),
|
|
config,
|
|
)
|
|
.await?;
|
|
} else {
|
|
socks = Socks5Stream::connect(opt.socks_server, opt.target_addr, opt.target_port, config)
|
|
.await?;
|
|
}
|
|
|
|
// Once connection is completed, can start to communicate with the server
|
|
http_request(&mut socks, domain).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Simple HTTP request
|
|
async fn http_request<T: AsyncRead + AsyncWrite + Unpin>(
|
|
stream: &mut T,
|
|
domain: String,
|
|
) -> Result<()> {
|
|
debug!("Requesting body...");
|
|
|
|
// construct our request, with a dynamic domain
|
|
let mut headers = vec![];
|
|
headers.extend_from_slice("GET / HTTP/1.1\r\nHost: ".as_bytes());
|
|
headers.extend_from_slice(domain.as_bytes());
|
|
headers
|
|
.extend_from_slice("\r\nUser-Agent: fast-socks5/0.1.0\r\nAccept: */*\r\n\r\n".as_bytes());
|
|
|
|
// flush headers
|
|
stream
|
|
.write_all(&headers)
|
|
.await
|
|
.context("Can't write HTTP Headers")?;
|
|
|
|
debug!("Reading body response...");
|
|
let mut result = [0u8; 1024];
|
|
// warning: read_to_end() method sometimes await forever when the web server
|
|
// doesn't write EOF char (\r\n\r\n).
|
|
// read() seems more appropriate
|
|
stream
|
|
.read(&mut result)
|
|
.await
|
|
.context("Can't read HTTP Response")?;
|
|
|
|
info!("Response: {}", String::from_utf8_lossy(&result));
|
|
|
|
if result.starts_with(b"HTTP/1.1") {
|
|
info!("HTTP/1.1 Response detected!");
|
|
}
|
|
//assert!(result.ends_with(b"</HTML>\r\n") || result.ends_with(b"</html>"));
|
|
|
|
Ok(())
|
|
}
|