use std::io;
use log::trace;
use shadowsocks::relay::socks5::{
self, Address, Command, Error as Socks5Error, HandshakeRequest, HandshakeResponse, PasswdAuthRequest,
PasswdAuthResponse, Reply, TcpRequestHeader, TcpResponseHeader,
};
use tokio::io::{AsyncRead, AsyncWrite};
use super::auth::Socks5Auth;
pub struct Socks5Negotiator;
impl Socks5Negotiator {
pub async fn handshake<S>(stream: &mut S, auth: &Socks5Auth) -> Result<(), Socks5Error>
where
S: AsyncRead + AsyncWrite + Unpin,
{
let methods = match auth {
Socks5Auth::None => vec![socks5::SOCKS5_AUTH_METHOD_NONE],
Socks5Auth::UsernamePassword { .. } => vec![
socks5::SOCKS5_AUTH_METHOD_PASSWORD,
socks5::SOCKS5_AUTH_METHOD_NONE,
],
};
let req = HandshakeRequest::new(methods);
trace!("socks5 client handshake: {:?}", req);
req.write_to(stream).await?;
let resp = HandshakeResponse::read_from(stream).await?;
trace!("socks5 handshake response: {:?}", resp);
match resp.chosen_method {
socks5::SOCKS5_AUTH_METHOD_NONE => Ok(()),
socks5::SOCKS5_AUTH_METHOD_PASSWORD => match auth {
Socks5Auth::UsernamePassword { username, password } => {
let req = PasswdAuthRequest::new(username.clone(), password.clone());
req.write_to(stream).await?;
let resp = PasswdAuthResponse::read_from(stream).await?;
if resp.status == 0 {
Ok(())
} else {
Err(Socks5Error::IoError(io::Error::other(format!(
"SOCKS5 username/password authentication failed with status {:#04x}",
resp.status
))))
}
}
Socks5Auth::None => Err(Socks5Error::IoError(io::Error::other(
"SOCKS5 proxy requested USERNAME/PASSWORD but no credentials were provided",
))),
},
method => Err(Socks5Error::IoError(io::Error::other(format!(
"SOCKS5 proxy selected unsupported authentication method {method:#04x}"
)))),
}
}
pub async fn command<S, A>(
stream: &mut S,
command: Command,
addr: A,
) -> Result<TcpResponseHeader, Socks5Error>
where
S: AsyncRead + AsyncWrite + Unpin,
A: Into<Address>,
{
let header = TcpRequestHeader::new(command, addr.into());
trace!("socks5 {command:?} request: {:?}", header);
header.write_to(stream).await?;
let resp = TcpResponseHeader::read_from(stream).await?;
trace!("socks5 {command:?} response: {:?}", resp);
match resp.reply {
Reply::Succeeded => Ok(resp),
reply => Err(Socks5Error::Reply(reply)),
}
}
pub async fn establish_tcp<S, A>(
stream: &mut S,
target: A,
auth: &Socks5Auth,
) -> Result<TcpResponseHeader, Socks5Error>
where
S: AsyncRead + AsyncWrite + Unpin,
A: Into<Address>,
{
Self::handshake(stream, auth).await?;
Self::command(stream, Command::TcpConnect, addr_helper(target)).await
}
pub async fn establish_udp_associate<S, A>(
stream: &mut S,
announce: A,
auth: &Socks5Auth,
) -> Result<TcpResponseHeader, Socks5Error>
where
S: AsyncRead + AsyncWrite + Unpin,
A: Into<Address>,
{
Self::handshake(stream, auth).await?;
Self::command(stream, Command::UdpAssociate, addr_helper(announce)).await
}
}
#[inline]
fn addr_helper<A: Into<Address>>(a: A) -> Address {
a.into()
}