mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-07 02:09:06 +00:00
e43537939a
1. clippy code 2. add fmt and clippy check in ci
843 lines
28 KiB
Rust
843 lines
28 KiB
Rust
use super::new_udp_header;
|
|
use super::parse_udp_request;
|
|
use super::read_exact;
|
|
use super::util::stream::tcp_connect_with_timeout;
|
|
use super::util::target_addr::{read_address, TargetAddr};
|
|
use super::Socks5Command;
|
|
use super::{consts, AuthenticationMethod, ReplyError, Result, SocksError};
|
|
use anyhow::Context;
|
|
use std::io;
|
|
use std::net::IpAddr;
|
|
use std::net::Ipv4Addr;
|
|
use std::net::{SocketAddr, ToSocketAddrs as StdToSocketAddrs};
|
|
use std::ops::Deref;
|
|
use std::pin::Pin;
|
|
use std::sync::Arc;
|
|
use std::task::Poll;
|
|
use tokio::io::AsyncReadExt;
|
|
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
|
|
use tokio::net::TcpStream;
|
|
use tokio::net::UdpSocket;
|
|
use tokio::try_join;
|
|
|
|
use tracing::{debug, error, info, trace};
|
|
|
|
#[derive(Clone)]
|
|
pub struct Config<A: Authentication = DenyAuthentication> {
|
|
/// Timeout of the command request
|
|
request_timeout: u64,
|
|
/// Avoid useless roundtrips if we don't need the Authentication layer
|
|
skip_auth: bool,
|
|
/// Enable dns-resolving
|
|
dns_resolve: bool,
|
|
/// Enable command execution
|
|
execute_command: bool,
|
|
/// Enable UDP support
|
|
allow_udp: bool,
|
|
/// For some complex scenarios, we may want to either accept Username/Password configuration
|
|
/// or IP Whitelisting, in case the client send only 1-2 auth methods (no auth) rather than 3 (with auth)
|
|
allow_no_auth: bool,
|
|
/// Contains the authentication trait to use the user against with
|
|
auth: Option<Arc<A>>,
|
|
}
|
|
|
|
impl<A: Authentication> Default for Config<A> {
|
|
fn default() -> Self {
|
|
Config {
|
|
request_timeout: 10,
|
|
skip_auth: false,
|
|
dns_resolve: true,
|
|
execute_command: true,
|
|
allow_udp: false,
|
|
allow_no_auth: false,
|
|
auth: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Use this trait to handle a custom authentication on your end.
|
|
#[async_trait::async_trait]
|
|
pub trait Authentication: Send + Sync {
|
|
type Item;
|
|
|
|
async fn authenticate(&self, credentials: Option<(String, String)>) -> Option<Self::Item>;
|
|
}
|
|
|
|
/// Basic user/pass auth method provided.
|
|
pub struct SimpleUserPassword {
|
|
pub username: String,
|
|
pub password: String,
|
|
}
|
|
|
|
/// The struct returned when the user has successfully authenticated
|
|
pub struct AuthSucceeded {
|
|
pub username: String,
|
|
}
|
|
|
|
/// This is an example to auth via simple credentials.
|
|
/// If the auth succeed, we return the username authenticated with, for further uses.
|
|
#[async_trait::async_trait]
|
|
impl Authentication for SimpleUserPassword {
|
|
type Item = AuthSucceeded;
|
|
|
|
async fn authenticate(&self, credentials: Option<(String, String)>) -> Option<Self::Item> {
|
|
if let Some((username, password)) = credentials {
|
|
// Client has supplied credentials
|
|
if username == self.username && password == self.password {
|
|
// Some() will allow the authentication and the credentials
|
|
// will be forwarded to the socket
|
|
Some(AuthSucceeded { username })
|
|
} else {
|
|
// Credentials incorrect, we deny the auth
|
|
None
|
|
}
|
|
} else {
|
|
// The client hasn't supplied any credentials, which only happens
|
|
// when `Config::allow_no_auth()` is set as `true`
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This will simply return Option::None, which denies the authentication
|
|
#[derive(Copy, Clone, Default)]
|
|
pub struct DenyAuthentication {}
|
|
|
|
#[async_trait::async_trait]
|
|
impl Authentication for DenyAuthentication {
|
|
type Item = ();
|
|
|
|
async fn authenticate(&self, _credentials: Option<(String, String)>) -> Option<Self::Item> {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// While this one will always allow the user in.
|
|
#[derive(Copy, Clone, Default)]
|
|
pub struct AcceptAuthentication {}
|
|
|
|
#[async_trait::async_trait]
|
|
impl Authentication for AcceptAuthentication {
|
|
type Item = ();
|
|
|
|
async fn authenticate(&self, _credentials: Option<(String, String)>) -> Option<Self::Item> {
|
|
Some(())
|
|
}
|
|
}
|
|
|
|
impl<A: Authentication> Config<A> {
|
|
/// How much time it should wait until the request timeout.
|
|
pub fn set_request_timeout(&mut self, n: u64) -> &mut Self {
|
|
self.request_timeout = n;
|
|
self
|
|
}
|
|
|
|
/// Skip the entire auth/handshake part, which means the server will directly wait for
|
|
/// the command request.
|
|
pub fn set_skip_auth(&mut self, value: bool) -> &mut Self {
|
|
self.skip_auth = value;
|
|
self.auth = None;
|
|
self
|
|
}
|
|
|
|
/// Enable authentication
|
|
/// 'static lifetime for Authentication avoid us to use `dyn Authentication`
|
|
/// and set the Arc before calling the function.
|
|
pub fn with_authentication<T: Authentication + 'static>(self, authentication: T) -> Config<T> {
|
|
Config {
|
|
request_timeout: self.request_timeout,
|
|
skip_auth: self.skip_auth,
|
|
dns_resolve: self.dns_resolve,
|
|
execute_command: self.execute_command,
|
|
allow_udp: self.allow_udp,
|
|
allow_no_auth: self.allow_no_auth,
|
|
auth: Some(Arc::new(authentication)),
|
|
}
|
|
}
|
|
|
|
/// For some complex scenarios, we may want to either accept Username/Password configuration
|
|
/// or IP Whitelisting, in case the client send only 2 auth methods rather than 3 (with auth)
|
|
pub fn set_allow_no_auth(&mut self, value: bool) -> &mut Self {
|
|
self.allow_no_auth = value;
|
|
self
|
|
}
|
|
|
|
/// Set whether or not to execute commands
|
|
pub fn set_execute_command(&mut self, value: bool) -> &mut Self {
|
|
self.execute_command = value;
|
|
self
|
|
}
|
|
|
|
/// Will the server perform dns resolve
|
|
pub fn set_dns_resolve(&mut self, value: bool) -> &mut Self {
|
|
self.dns_resolve = value;
|
|
self
|
|
}
|
|
|
|
/// Set whether or not to allow udp traffic
|
|
pub fn set_udp_support(&mut self, value: bool) -> &mut Self {
|
|
self.allow_udp = value;
|
|
self
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
pub trait AsyncTcpConnector {
|
|
type S: AsyncRead + AsyncWrite + Unpin + Send + Sync;
|
|
|
|
async fn tcp_connect(&self, addr: SocketAddr, timeout_s: u64) -> Result<Self::S>;
|
|
}
|
|
|
|
pub struct DefaultTcpConnector {}
|
|
|
|
#[async_trait::async_trait]
|
|
impl AsyncTcpConnector for DefaultTcpConnector {
|
|
type S = TcpStream;
|
|
|
|
async fn tcp_connect(&self, addr: SocketAddr, timeout_s: u64) -> Result<TcpStream> {
|
|
tcp_connect_with_timeout(addr, timeout_s).await
|
|
}
|
|
}
|
|
|
|
/// Wrap TcpStream and contains Socks5 protocol implementation.
|
|
pub struct Socks5Socket<T: AsyncRead + AsyncWrite + Unpin, A: Authentication, C: AsyncTcpConnector>
|
|
{
|
|
inner: T,
|
|
config: Arc<Config<A>>,
|
|
auth: AuthenticationMethod,
|
|
target_addr: Option<TargetAddr>,
|
|
cmd: Option<Socks5Command>,
|
|
/// Socket address which will be used in the reply message.
|
|
reply_ip: Option<IpAddr>,
|
|
/// If the client has been authenticated, that's where we store his credentials
|
|
/// to be accessed from the socket
|
|
credentials: Option<A::Item>,
|
|
tcp_connector: C,
|
|
}
|
|
|
|
impl<T: AsyncRead + AsyncWrite + Unpin, A: Authentication, C: AsyncTcpConnector>
|
|
Socks5Socket<T, A, C>
|
|
{
|
|
pub fn new(socket: T, config: Arc<Config<A>>, tcp_connector: C) -> Self {
|
|
Socks5Socket {
|
|
inner: socket,
|
|
config,
|
|
auth: AuthenticationMethod::None,
|
|
target_addr: None,
|
|
cmd: None,
|
|
reply_ip: None,
|
|
credentials: None,
|
|
tcp_connector,
|
|
}
|
|
}
|
|
|
|
/// Set the bind IP address in Socks5Reply.
|
|
///
|
|
/// Only the inner socket owner knows the correct reply bind addr, so leave this field to be
|
|
/// populated. For those strict clients, users can use this function to set the correct IP
|
|
/// address.
|
|
///
|
|
/// Most popular SOCKS5 clients [1] [2] ignore BND.ADDR and BND.PORT the reply of command
|
|
/// CONNECT, but this field could be useful in some other command, such as UDP ASSOCIATE.
|
|
///
|
|
/// [1]: https://github.com/chromium/chromium/blob/bd2c7a8b65ec42d806277dd30f138a673dec233a/net/socket/socks5_client_socket.cc#L481
|
|
/// [2]: https://github.com/curl/curl/blob/d15692ebbad5e9cfb871b0f7f51a73e43762cee2/lib/socks.c#L978
|
|
pub fn set_reply_ip(&mut self, addr: IpAddr) {
|
|
self.reply_ip = Some(addr);
|
|
}
|
|
|
|
/// Process clients SOCKS requests
|
|
/// This is the entry point where a whole request is processed.
|
|
pub async fn upgrade_to_socks5(mut self) -> Result<Socks5Socket<T, A, C>> {
|
|
trace!("upgrading to socks5...");
|
|
|
|
// Handshake
|
|
if !self.config.skip_auth {
|
|
let methods = self.get_methods().await?;
|
|
|
|
let auth_method = self.can_accept_method(methods).await?;
|
|
|
|
if self.config.auth.is_some() {
|
|
let credentials = self.authenticate(auth_method).await?;
|
|
self.credentials = Some(credentials);
|
|
}
|
|
} else {
|
|
debug!("skipping auth");
|
|
}
|
|
|
|
match self.request().await {
|
|
Ok(_) => {}
|
|
Err(SocksError::ReplyError(e)) => {
|
|
// If a reply error has been returned, we send it to the client
|
|
self.reply_error(&e).await?;
|
|
return Err(e.into()); // propagate the error to end this connection's task
|
|
}
|
|
// if any other errors has been detected, we simply end connection's task
|
|
Err(d) => return Err(d),
|
|
};
|
|
|
|
Ok(self)
|
|
}
|
|
|
|
/// Consumes the `Socks5Socket`, returning the wrapped stream.
|
|
pub fn into_inner(self) -> T {
|
|
self.inner
|
|
}
|
|
|
|
/// Read the authentication method provided by the client.
|
|
/// A client send a list of methods that he supports, he could send
|
|
///
|
|
/// - 0: Non auth
|
|
/// - 2: Auth with username/password
|
|
///
|
|
/// Altogether, then the server choose to use of of these,
|
|
/// or deny the handshake (thus the connection).
|
|
///
|
|
/// # Examples
|
|
/// ```text
|
|
/// {SOCKS Version, methods-length}
|
|
/// eg. (non-auth) {5, 2}
|
|
/// eg. (auth) {5, 3}
|
|
/// ```
|
|
///
|
|
async fn get_methods(&mut self) -> Result<Vec<u8>> {
|
|
trace!("Socks5Socket: get_methods()");
|
|
// read the first 2 bytes which contains the SOCKS version and the methods len()
|
|
let [version, methods_len] =
|
|
read_exact!(self.inner, [0u8; 2]).context("Can't read methods")?;
|
|
debug!(
|
|
"Handshake headers: [version: {version}, methods len: {len}]",
|
|
version = version,
|
|
len = methods_len,
|
|
);
|
|
|
|
if version != consts::SOCKS5_VERSION {
|
|
return Err(SocksError::UnsupportedSocksVersion(version));
|
|
}
|
|
|
|
// {METHODS available from the client}
|
|
// eg. (non-auth) {0, 1}
|
|
// eg. (auth) {0, 1, 2}
|
|
let methods = read_exact!(self.inner, vec![0u8; methods_len as usize])
|
|
.context("Can't get methods.")?;
|
|
debug!("methods supported sent by the client: {:?}", &methods);
|
|
|
|
// Return methods available
|
|
Ok(methods)
|
|
}
|
|
|
|
/// Decide to whether or not, accept the authentication method.
|
|
/// Don't forget that the methods list sent by the client, contains one or more methods.
|
|
///
|
|
/// # Request
|
|
///
|
|
/// Client send an array of 3 entries: [0, 1, 2]
|
|
/// ```text
|
|
/// {SOCKS Version, Authentication chosen}
|
|
/// eg. (non-auth) {5, 0}
|
|
/// eg. (GSSAPI) {5, 1}
|
|
/// eg. (auth) {5, 2}
|
|
/// ```
|
|
///
|
|
/// # Response
|
|
/// ```text
|
|
/// eg. (accept non-auth) {5, 0x00}
|
|
/// eg. (non-acceptable) {5, 0xff}
|
|
/// ```
|
|
///
|
|
async fn can_accept_method(&mut self, client_methods: Vec<u8>) -> Result<u8> {
|
|
let method_supported;
|
|
|
|
if let Some(_auth) = self.config.auth.as_ref() {
|
|
if client_methods.contains(&consts::SOCKS5_AUTH_METHOD_PASSWORD) {
|
|
// can auth with password
|
|
method_supported = consts::SOCKS5_AUTH_METHOD_PASSWORD;
|
|
} else {
|
|
// client hasn't provided a password
|
|
if self.config.allow_no_auth {
|
|
// but we allow no auth, for ip whitelisting
|
|
method_supported = consts::SOCKS5_AUTH_METHOD_NONE;
|
|
} else {
|
|
// we don't allow no auth, so we deny the entry
|
|
debug!("Don't support this auth method, reply with (0xff)");
|
|
self.inner
|
|
.write_all(&[
|
|
consts::SOCKS5_VERSION,
|
|
consts::SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE,
|
|
])
|
|
.await
|
|
.context("Can't reply with method not acceptable.")?;
|
|
|
|
return Err(SocksError::AuthMethodUnacceptable(client_methods));
|
|
}
|
|
}
|
|
} else {
|
|
method_supported = consts::SOCKS5_AUTH_METHOD_NONE;
|
|
}
|
|
|
|
debug!(
|
|
"Reply with method {} ({})",
|
|
AuthenticationMethod::from_u8(method_supported).context("Method not supported")?,
|
|
method_supported
|
|
);
|
|
self.inner
|
|
.write(&[consts::SOCKS5_VERSION, method_supported])
|
|
.await
|
|
.context("Can't reply with method auth-none")?;
|
|
Ok(method_supported)
|
|
}
|
|
|
|
async fn read_username_password(socket: &mut T) -> Result<(String, String)> {
|
|
trace!("Socks5Socket: authenticate()");
|
|
let [version, user_len] = read_exact!(socket, [0u8; 2]).context("Can't read user len")?;
|
|
debug!(
|
|
"Auth: [version: {version}, user len: {len}]",
|
|
version = version,
|
|
len = user_len,
|
|
);
|
|
|
|
if user_len < 1 {
|
|
return Err(SocksError::AuthenticationFailed(format!(
|
|
"Username malformed ({} chars)",
|
|
user_len
|
|
)));
|
|
}
|
|
|
|
let username =
|
|
read_exact!(socket, vec![0u8; user_len as usize]).context("Can't get username.")?;
|
|
debug!("username bytes: {:?}", &username);
|
|
|
|
let [pass_len] = read_exact!(socket, [0u8; 1]).context("Can't read pass len")?;
|
|
debug!("Auth: [pass len: {len}]", len = pass_len,);
|
|
|
|
if pass_len < 1 {
|
|
return Err(SocksError::AuthenticationFailed(format!(
|
|
"Password malformed ({} chars)",
|
|
pass_len
|
|
)));
|
|
}
|
|
|
|
let password =
|
|
read_exact!(socket, vec![0u8; pass_len as usize]).context("Can't get password.")?;
|
|
debug!("password bytes: {:?}", &password);
|
|
|
|
let username = String::from_utf8(username).context("Failed to convert username")?;
|
|
let password = String::from_utf8(password).context("Failed to convert password")?;
|
|
|
|
Ok((username, password))
|
|
}
|
|
|
|
/// Only called if
|
|
/// - this server has `Authentication` trait implemented.
|
|
/// - and the client supports authentication via username/password
|
|
/// - or the client doesn't send authentication, but we let the trait decides if the `allow_no_auth()` set as `true`
|
|
async fn authenticate(&mut self, auth_method: u8) -> Result<A::Item> {
|
|
let credentials = if auth_method == consts::SOCKS5_AUTH_METHOD_PASSWORD {
|
|
let credentials = Self::read_username_password(&mut self.inner).await?;
|
|
Some(credentials)
|
|
} else {
|
|
// the client hasn't provided any credentials, the function auth.authenticate()
|
|
// will then check None, according to other parameters provided by the trait
|
|
// such as IP, etc.
|
|
None
|
|
};
|
|
|
|
let auth = self.config.auth.as_ref().context("No auth module")?;
|
|
|
|
if let Some(credentials) = auth.authenticate(credentials).await {
|
|
if auth_method == consts::SOCKS5_AUTH_METHOD_PASSWORD {
|
|
// only the password way expect to write a response at this moment
|
|
self.inner
|
|
.write_all(&[1, consts::SOCKS5_REPLY_SUCCEEDED])
|
|
.await
|
|
.context("Can't reply auth success")?;
|
|
}
|
|
|
|
info!("User logged successfully.");
|
|
|
|
Ok(credentials)
|
|
} else {
|
|
self.inner
|
|
.write_all(&[1, consts::SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE])
|
|
.await
|
|
.context("Can't reply with auth method not acceptable.")?;
|
|
|
|
Err(SocksError::AuthenticationRejected(
|
|
"Authentication, rejected.".to_string(),
|
|
))
|
|
}
|
|
}
|
|
|
|
/// Wrapper to principally cover ReplyError types for both functions read & execute request.
|
|
async fn request(&mut self) -> Result<()> {
|
|
self.read_command().await?;
|
|
|
|
if self.config.dns_resolve {
|
|
self.resolve_dns().await?;
|
|
} else {
|
|
debug!("Domain won't be resolved because `dns_resolve`'s config has been turned off.")
|
|
}
|
|
|
|
if self.config.execute_command {
|
|
self.execute_command().await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Reply error to the client with the reply code according to the RFC.
|
|
async fn reply_error(&mut self, error: &ReplyError) -> Result<()> {
|
|
let reply = new_reply(error, "0.0.0.0:0".parse().unwrap());
|
|
debug!("reply error to be written: {:?}", &reply);
|
|
|
|
self.inner
|
|
.write(&reply)
|
|
.await
|
|
.context("Can't write the reply!")?;
|
|
|
|
self.inner.flush().await.context("Can't flush the reply!")?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Decide to whether or not, accept the authentication method.
|
|
/// Don't forget that the methods list sent by the client, contains one or more methods.
|
|
///
|
|
/// # Request
|
|
/// ```text
|
|
/// +----+-----+-------+------+----------+----------+
|
|
/// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
|
|
/// +----+-----+-------+------+----------+----------+
|
|
/// | 1 | 1 | 1 | 1 | Variable | 2 |
|
|
/// +----+-----+-------+------+----------+----------+
|
|
/// ```
|
|
///
|
|
/// It the request is correct, it should returns a ['SocketAddr'].
|
|
///
|
|
async fn read_command(&mut self) -> Result<()> {
|
|
let [version, cmd, rsv, address_type] =
|
|
read_exact!(self.inner, [0u8; 4]).context("Malformed request")?;
|
|
debug!(
|
|
"Request: [version: {version}, command: {cmd}, rev: {rsv}, address_type: {address_type}]",
|
|
version = version,
|
|
cmd = cmd,
|
|
rsv = rsv,
|
|
address_type = address_type,
|
|
);
|
|
|
|
if version != consts::SOCKS5_VERSION {
|
|
return Err(SocksError::UnsupportedSocksVersion(version));
|
|
}
|
|
|
|
match Socks5Command::from_u8(cmd) {
|
|
None => return Err(ReplyError::CommandNotSupported.into()),
|
|
Some(cmd) => match cmd {
|
|
Socks5Command::TCPConnect => {
|
|
self.cmd = Some(cmd);
|
|
}
|
|
Socks5Command::UDPAssociate => {
|
|
if !self.config.allow_udp {
|
|
return Err(ReplyError::CommandNotSupported.into());
|
|
}
|
|
self.cmd = Some(cmd);
|
|
}
|
|
Socks5Command::TCPBind => return Err(ReplyError::CommandNotSupported.into()),
|
|
},
|
|
}
|
|
|
|
// Guess address type
|
|
let target_addr = read_address(&mut self.inner, address_type)
|
|
.await
|
|
.map_err(|e| {
|
|
// print explicit error
|
|
error!("{:#}", e);
|
|
// then convert it to a reply
|
|
ReplyError::AddressTypeNotSupported
|
|
})?;
|
|
|
|
self.target_addr = Some(target_addr);
|
|
|
|
debug!("Request target is {}", self.target_addr.as_ref().unwrap());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// This function is public, it can be call manually on your own-willing
|
|
/// if config flag has been turned off: `Config::dns_resolve == false`.
|
|
pub async fn resolve_dns(&mut self) -> Result<()> {
|
|
trace!("resolving dns");
|
|
if let Some(target_addr) = self.target_addr.take() {
|
|
// decide whether we have to resolve DNS or not
|
|
self.target_addr = match target_addr {
|
|
TargetAddr::Domain(_, _) => Some(target_addr.resolve_dns().await?),
|
|
TargetAddr::Ip(_) => Some(target_addr),
|
|
};
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Execute the socks5 command that the client wants.
|
|
async fn execute_command(&mut self) -> Result<()> {
|
|
match &self.cmd {
|
|
None => Err(ReplyError::CommandNotSupported.into()),
|
|
Some(cmd) => match cmd {
|
|
Socks5Command::TCPBind => Err(ReplyError::CommandNotSupported.into()),
|
|
Socks5Command::TCPConnect => return self.execute_command_connect().await,
|
|
Socks5Command::UDPAssociate => {
|
|
if self.config.allow_udp {
|
|
return self.execute_command_udp_assoc().await;
|
|
} else {
|
|
Err(ReplyError::CommandNotSupported.into())
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Connect to the target address that the client wants,
|
|
/// then forward the data between them (client <=> target address).
|
|
async fn execute_command_connect(&mut self) -> Result<()> {
|
|
// async-std's ToSocketAddrs doesn't supports external trait implementation
|
|
// @see https://github.com/async-rs/async-std/issues/539
|
|
let addr = self
|
|
.target_addr
|
|
.as_ref()
|
|
.context("target_addr empty")?
|
|
.to_socket_addrs()?
|
|
.next()
|
|
.context("unreachable")?;
|
|
|
|
// TCP connect with timeout, to avoid memory leak for connection that takes forever
|
|
let outbound = self
|
|
.tcp_connector
|
|
.tcp_connect(addr, self.config.request_timeout)
|
|
.await?;
|
|
|
|
debug!("Connected to remote destination");
|
|
|
|
self.inner
|
|
.write(&new_reply(
|
|
&ReplyError::Succeeded,
|
|
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0),
|
|
))
|
|
.await
|
|
.context("Can't write successful reply")?;
|
|
|
|
self.inner.flush().await.context("Can't flush the reply!")?;
|
|
|
|
debug!("Wrote success");
|
|
|
|
transfer(&mut self.inner, outbound).await
|
|
}
|
|
|
|
/// Bind to a random UDP port, wait for the traffic from
|
|
/// the client, and then forward the data to the remote addr.
|
|
async fn execute_command_udp_assoc(&mut self) -> Result<()> {
|
|
// The DST.ADDR and DST.PORT fields contain the address and port that
|
|
// the client expects to use to send UDP datagrams on for the
|
|
// association. The server MAY use this information to limit access
|
|
// to the association.
|
|
// @see Page 6, https://datatracker.ietf.org/doc/html/rfc1928.
|
|
//
|
|
// We do NOT limit the access from the client currently in this implementation.
|
|
let _not_used = self.target_addr.as_ref();
|
|
|
|
// Listen with UDP6 socket, so the client can connect to it with either
|
|
// IPv4 or IPv6.
|
|
let peer_sock = UdpSocket::bind("[::]:0").await?;
|
|
|
|
// Respect the pre-populated reply IP address.
|
|
self.inner
|
|
.write(&new_reply(
|
|
&ReplyError::Succeeded,
|
|
SocketAddr::new(
|
|
self.reply_ip.context("invalid reply ip")?,
|
|
peer_sock.local_addr()?.port(),
|
|
),
|
|
))
|
|
.await
|
|
.context("Can't write successful reply")?;
|
|
|
|
debug!("Wrote success");
|
|
|
|
transfer_udp(peer_sock).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn target_addr(&self) -> Option<&TargetAddr> {
|
|
self.target_addr.as_ref()
|
|
}
|
|
|
|
pub fn auth(&self) -> &AuthenticationMethod {
|
|
&self.auth
|
|
}
|
|
|
|
pub fn cmd(&self) -> &Option<Socks5Command> {
|
|
&self.cmd
|
|
}
|
|
|
|
/// Borrow the credentials of the user has authenticated with
|
|
pub fn get_credentials(&self) -> Option<&<<A as Authentication>::Item as Deref>::Target>
|
|
where
|
|
<A as Authentication>::Item: Deref,
|
|
{
|
|
self.credentials.as_deref()
|
|
}
|
|
|
|
/// Get the credentials of the user has authenticated with
|
|
pub fn take_credentials(&mut self) -> Option<A::Item> {
|
|
self.credentials.take()
|
|
}
|
|
|
|
pub fn tcp_connector(&self) -> &C {
|
|
&self.tcp_connector
|
|
}
|
|
}
|
|
|
|
/// Copy data between two peers
|
|
/// Using 2 different generators, because they could be different structs with same traits.
|
|
async fn transfer<I, O>(mut inbound: I, mut outbound: O) -> Result<()>
|
|
where
|
|
I: AsyncRead + AsyncWrite + Unpin,
|
|
O: AsyncRead + AsyncWrite + Unpin,
|
|
{
|
|
match tokio::io::copy_bidirectional(&mut inbound, &mut outbound).await {
|
|
Ok(res) => info!("transfer closed ({}, {})", res.0, res.1),
|
|
Err(err) => error!("transfer error: {:?}", err),
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn handle_udp_request(inbound: &UdpSocket, outbound: &UdpSocket) -> Result<()> {
|
|
let mut buf = vec![0u8; 0x10000];
|
|
loop {
|
|
let (size, client_addr) = inbound.recv_from(&mut buf).await?;
|
|
debug!("Server recieve udp from {}", client_addr);
|
|
inbound.connect(client_addr).await?;
|
|
|
|
let (frag, target_addr, data) = parse_udp_request(&buf[..size]).await?;
|
|
|
|
if frag != 0 {
|
|
debug!("Discard UDP frag packets sliently.");
|
|
return Ok(());
|
|
}
|
|
|
|
debug!("Server forward to packet to {}", target_addr);
|
|
let mut target_addr = target_addr
|
|
.to_socket_addrs()?
|
|
.next()
|
|
.context("unreachable")?;
|
|
|
|
target_addr.set_ip(match target_addr.ip() {
|
|
std::net::IpAddr::V4(v4) => std::net::IpAddr::V6(v4.to_ipv6_mapped()),
|
|
v6 @ std::net::IpAddr::V6(_) => v6,
|
|
});
|
|
outbound.send_to(data, target_addr).await?;
|
|
}
|
|
}
|
|
|
|
async fn handle_udp_response(inbound: &UdpSocket, outbound: &UdpSocket) -> Result<()> {
|
|
let mut buf = vec![0u8; 0x10000];
|
|
loop {
|
|
let (size, remote_addr) = outbound.recv_from(&mut buf).await?;
|
|
debug!("Recieve packet from {}", remote_addr);
|
|
|
|
let mut data = new_udp_header(remote_addr)?;
|
|
data.extend_from_slice(&buf[..size]);
|
|
inbound.send(&data).await?;
|
|
}
|
|
}
|
|
|
|
async fn transfer_udp(inbound: UdpSocket) -> Result<()> {
|
|
let outbound = UdpSocket::bind("[::]:0").await?;
|
|
|
|
let req_fut = handle_udp_request(&inbound, &outbound);
|
|
let res_fut = handle_udp_response(&inbound, &outbound);
|
|
match try_join!(req_fut, res_fut) {
|
|
Ok(_) => {}
|
|
Err(error) => return Err(error),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Fixes the issue "cannot borrow data in dereference of `Pin<&mut >` as mutable"
|
|
//
|
|
// cf. https://users.rust-lang.org/t/take-in-impl-future-cannot-borrow-data-in-a-dereference-of-pin/52042
|
|
impl<T, A: Authentication, S: AsyncTcpConnector> Unpin for Socks5Socket<T, A, S> where
|
|
T: AsyncRead + AsyncWrite + Unpin
|
|
{
|
|
}
|
|
|
|
/// Allow us to read directly from the struct
|
|
impl<T, A: Authentication, S: AsyncTcpConnector> AsyncRead for Socks5Socket<T, A, S>
|
|
where
|
|
T: AsyncRead + AsyncWrite + Unpin,
|
|
{
|
|
fn poll_read(
|
|
mut self: Pin<&mut Self>,
|
|
context: &mut std::task::Context,
|
|
buf: &mut tokio::io::ReadBuf<'_>,
|
|
) -> Poll<std::io::Result<()>> {
|
|
Pin::new(&mut self.inner).poll_read(context, buf)
|
|
}
|
|
}
|
|
|
|
/// Allow us to write directly into the struct
|
|
impl<T, A: Authentication, S: AsyncTcpConnector> AsyncWrite for Socks5Socket<T, A, S>
|
|
where
|
|
T: AsyncRead + AsyncWrite + Unpin,
|
|
{
|
|
fn poll_write(
|
|
mut self: Pin<&mut Self>,
|
|
context: &mut std::task::Context,
|
|
buf: &[u8],
|
|
) -> Poll<io::Result<usize>> {
|
|
Pin::new(&mut self.inner).poll_write(context, buf)
|
|
}
|
|
|
|
fn poll_flush(
|
|
mut self: Pin<&mut Self>,
|
|
context: &mut std::task::Context,
|
|
) -> Poll<io::Result<()>> {
|
|
Pin::new(&mut self.inner).poll_flush(context)
|
|
}
|
|
|
|
fn poll_shutdown(
|
|
mut self: Pin<&mut Self>,
|
|
context: &mut std::task::Context,
|
|
) -> Poll<io::Result<()>> {
|
|
Pin::new(&mut self.inner).poll_shutdown(context)
|
|
}
|
|
}
|
|
|
|
/// Generate reply code according to the RFC.
|
|
fn new_reply(error: &ReplyError, sock_addr: SocketAddr) -> Vec<u8> {
|
|
let (addr_type, mut ip_oct, mut port) = match sock_addr {
|
|
SocketAddr::V4(sock) => (
|
|
consts::SOCKS5_ADDR_TYPE_IPV4,
|
|
sock.ip().octets().to_vec(),
|
|
sock.port().to_be_bytes().to_vec(),
|
|
),
|
|
SocketAddr::V6(sock) => (
|
|
consts::SOCKS5_ADDR_TYPE_IPV6,
|
|
sock.ip().octets().to_vec(),
|
|
sock.port().to_be_bytes().to_vec(),
|
|
),
|
|
};
|
|
|
|
let mut reply = vec![
|
|
consts::SOCKS5_VERSION,
|
|
error.as_u8(), // transform the error into byte code
|
|
0x00, // reserved
|
|
addr_type, // address type (ipv4, v6, domain)
|
|
];
|
|
reply.append(&mut ip_oct);
|
|
reply.append(&mut port);
|
|
|
|
reply
|
|
}
|