#[forbid(unsafe_code)]
#[macro_use]
extern crate log;
use anyhow::Context;
use fast_socks5::{
server::{run_tcp_proxy, run_udp_proxy, DnsResolveHelper as _, Socks5ServerProtocol},
ReplyError, Result, Socks5Command, SocksError,
};
use std::{future::Future, num::ParseFloatError, time::Duration};
use structopt::StructOpt;
use tokio::net::TcpListener;
use tokio::task;
#[derive(Debug, StructOpt)]
#[structopt(
name = "socks5-server",
about = "A simple implementation of a socks5-server."
)]
struct Opt {
#[structopt(short, long)]
pub listen_addr: String,
#[structopt(long)]
pub public_addr: Option<std::net::IpAddr>,
#[structopt(short = "t", long, default_value = "10", parse(try_from_str=parse_duration))]
pub request_timeout: Duration,
#[structopt(subcommand, name = "auth")] pub auth: AuthMode,
#[structopt(short = "k", long)]
pub skip_auth: bool,
#[structopt(short = "U", long)]
pub allow_udp: bool,
}
#[derive(StructOpt, Debug, PartialEq)]
enum AuthMode {
NoAuth,
Password {
#[structopt(short, long)]
username: String,
#[structopt(short, long)]
password: String,
},
}
fn parse_duration(s: &str) -> Result<Duration, ParseFloatError> {
let seconds = s.parse()?;
Ok(Duration::from_secs_f64(seconds))
}
#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();
spawn_socks_server().await
}
async fn spawn_socks_server() -> Result<()> {
let opt: &'static Opt = Box::leak(Box::new(Opt::from_args()));
if opt.allow_udp && opt.public_addr.is_none() {
return Err(SocksError::ArgumentInputError(
"Can't allow UDP if public-addr is not set",
));
}
if opt.skip_auth && opt.auth != AuthMode::NoAuth {
return Err(SocksError::ArgumentInputError(
"Can't use skip-auth flag and authentication altogether.",
));
}
let listener = TcpListener::bind(&opt.listen_addr).await?;
info!("Listen for socks connections @ {}", &opt.listen_addr);
loop {
match listener.accept().await {
Ok((socket, _client_addr)) => {
spawn_and_log_error(serve_socks5(opt, socket));
}
Err(err) => {
error!("accept error = {:?}", err);
}
}
}
}
async fn serve_socks5(opt: &Opt, socket: tokio::net::TcpStream) -> Result<(), SocksError> {
let (proto, cmd, target_addr) = match &opt.auth {
AuthMode::NoAuth if opt.skip_auth => {
Socks5ServerProtocol::skip_auth_this_is_not_rfc_compliant(socket)
}
AuthMode::NoAuth => Socks5ServerProtocol::accept_no_auth(socket).await?,
AuthMode::Password { username, password } => {
Socks5ServerProtocol::accept_password_auth(socket, |user, pass| {
user == *username && pass == *password
})
.await?
.0
}
}
.read_command()
.await?
.resolve_dns()
.await?;
match cmd {
Socks5Command::TCPConnect => {
run_tcp_proxy(proto, &target_addr, opt.request_timeout, false).await?;
}
Socks5Command::UDPAssociate if opt.allow_udp => {
let reply_ip = opt.public_addr.context("invalid reply ip")?;
run_udp_proxy(proto, &target_addr, None, reply_ip, None).await?;
}
_ => {
proto.reply_error(&ReplyError::CommandNotSupported).await?;
return Err(ReplyError::CommandNotSupported.into());
}
};
Ok(())
}
fn spawn_and_log_error<F>(fut: F) -> task::JoinHandle<()>
where
F: Future<Output = Result<()>> + Send + 'static,
{
task::spawn(async move {
match fut.await {
Ok(()) => {}
Err(err) => error!("{:#}", &err),
}
})
}