#![forbid(unsafe_code)]
#[macro_use]
extern crate log;
pub mod client;
pub mod server;
pub mod util;
#[cfg(feature = "socks4")]
pub mod socks4;
use anyhow::Context;
use std::fmt;
use std::io;
use thiserror::Error;
use util::target_addr::read_address;
use util::target_addr::TargetAddr;
use util::target_addr::ToTargetAddr;
use tokio::io::AsyncReadExt;
#[rustfmt::skip]
pub mod consts {
pub const SOCKS5_VERSION: u8 = 0x05;
pub const SOCKS5_AUTH_METHOD_NONE: u8 = 0x00;
pub const SOCKS5_AUTH_METHOD_GSSAPI: u8 = 0x01;
pub const SOCKS5_AUTH_METHOD_PASSWORD: u8 = 0x02;
pub const SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE: u8 = 0xff;
pub const SOCKS5_CMD_TCP_CONNECT: u8 = 0x01;
pub const SOCKS5_CMD_TCP_BIND: u8 = 0x02;
pub const SOCKS5_CMD_UDP_ASSOCIATE: u8 = 0x03;
pub const SOCKS5_ADDR_TYPE_IPV4: u8 = 0x01;
pub const SOCKS5_ADDR_TYPE_DOMAIN_NAME: u8 = 0x03;
pub const SOCKS5_ADDR_TYPE_IPV6: u8 = 0x04;
pub const SOCKS5_REPLY_SUCCEEDED: u8 = 0x00;
pub const SOCKS5_REPLY_GENERAL_FAILURE: u8 = 0x01;
pub const SOCKS5_REPLY_CONNECTION_NOT_ALLOWED: u8 = 0x02;
pub const SOCKS5_REPLY_NETWORK_UNREACHABLE: u8 = 0x03;
pub const SOCKS5_REPLY_HOST_UNREACHABLE: u8 = 0x04;
pub const SOCKS5_REPLY_CONNECTION_REFUSED: u8 = 0x05;
pub const SOCKS5_REPLY_TTL_EXPIRED: u8 = 0x06;
pub const SOCKS5_REPLY_COMMAND_NOT_SUPPORTED: u8 = 0x07;
pub const SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED: u8 = 0x08;
}
#[derive(Debug, PartialEq)]
pub enum Socks5Command {
TCPConnect,
TCPBind,
UDPAssociate,
}
#[allow(dead_code)]
impl Socks5Command {
#[inline]
#[rustfmt::skip]
fn as_u8(&self) -> u8 {
match self {
Socks5Command::TCPConnect => consts::SOCKS5_CMD_TCP_CONNECT,
Socks5Command::TCPBind => consts::SOCKS5_CMD_TCP_BIND,
Socks5Command::UDPAssociate => consts::SOCKS5_CMD_UDP_ASSOCIATE,
}
}
#[inline]
#[rustfmt::skip]
fn from_u8(code: u8) -> Option<Socks5Command> {
match code {
consts::SOCKS5_CMD_TCP_CONNECT => Some(Socks5Command::TCPConnect),
consts::SOCKS5_CMD_TCP_BIND => Some(Socks5Command::TCPBind),
consts::SOCKS5_CMD_UDP_ASSOCIATE => Some(Socks5Command::UDPAssociate),
_ => None,
}
}
}
#[derive(Debug, PartialEq)]
pub enum AuthenticationMethod {
None,
Password { username: String, password: String },
}
impl AuthenticationMethod {
#[inline]
#[rustfmt::skip]
fn as_u8(&self) -> u8 {
match self {
AuthenticationMethod::None => consts::SOCKS5_AUTH_METHOD_NONE,
AuthenticationMethod::Password {..} =>
consts::SOCKS5_AUTH_METHOD_PASSWORD
}
}
#[inline]
#[rustfmt::skip]
fn from_u8(code: u8) -> Option<AuthenticationMethod> {
match code {
consts::SOCKS5_AUTH_METHOD_NONE => Some(AuthenticationMethod::None),
consts::SOCKS5_AUTH_METHOD_PASSWORD => Some(AuthenticationMethod::Password { username: "test".to_string(), password: "test".to_string()}),
_ => None,
}
}
}
impl fmt::Display for AuthenticationMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
AuthenticationMethod::None => f.write_str("AuthenticationMethod::None"),
AuthenticationMethod::Password { .. } => f.write_str("AuthenticationMethod::Password"),
}
}
}
#[derive(Error, Debug)]
pub enum SocksError {
#[error("i/o error: {0}")]
Io(#[from] io::Error),
#[error("the data for key `{0}` is not available")]
Redaction(String),
#[error("invalid header (expected {expected:?}, found {found:?})")]
InvalidHeader { expected: String, found: String },
#[error("Auth method unacceptable `{0:?}`.")]
AuthMethodUnacceptable(Vec<u8>),
#[error("Unsupported SOCKS version `{0}`.")]
UnsupportedSocksVersion(u8),
#[error("Domain exceeded max sequence length")]
ExceededMaxDomainLen(usize),
#[error("Authentication failed `{0}`")]
AuthenticationFailed(String),
#[error("Authentication rejected `{0}`")]
AuthenticationRejected(String),
#[error("Error with reply: {0}.")]
ReplyError(#[from] ReplyError),
#[cfg(feature = "socks4")]
#[error("Error with reply: {0}.")]
ReplySocks4Error(#[from] socks4::ReplyError),
#[error("Argument input error: `{0}`.")]
ArgumentInputError(&'static str),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
pub type Result<T, E = SocksError> = core::result::Result<T, E>;
#[derive(Error, Debug, Copy, Clone)]
pub enum ReplyError {
#[error("Succeeded")]
Succeeded,
#[error("General failure")]
GeneralFailure,
#[error("Connection not allowed by ruleset")]
ConnectionNotAllowed,
#[error("Network unreachable")]
NetworkUnreachable,
#[error("Host unreachable")]
HostUnreachable,
#[error("Connection refused")]
ConnectionRefused,
#[error("Connection timeout")]
ConnectionTimeout,
#[error("TTL expired")]
TtlExpired,
#[error("Command not supported")]
CommandNotSupported,
#[error("Address type not supported")]
AddressTypeNotSupported,
}
impl ReplyError {
#[inline]
#[rustfmt::skip]
pub fn as_u8(self) -> u8 {
match self {
ReplyError::Succeeded => consts::SOCKS5_REPLY_SUCCEEDED,
ReplyError::GeneralFailure => consts::SOCKS5_REPLY_GENERAL_FAILURE,
ReplyError::ConnectionNotAllowed => consts::SOCKS5_REPLY_CONNECTION_NOT_ALLOWED,
ReplyError::NetworkUnreachable => consts::SOCKS5_REPLY_NETWORK_UNREACHABLE,
ReplyError::HostUnreachable => consts::SOCKS5_REPLY_HOST_UNREACHABLE,
ReplyError::ConnectionRefused => consts::SOCKS5_REPLY_CONNECTION_REFUSED,
ReplyError::ConnectionTimeout => consts::SOCKS5_REPLY_TTL_EXPIRED,
ReplyError::TtlExpired => consts::SOCKS5_REPLY_TTL_EXPIRED,
ReplyError::CommandNotSupported => consts::SOCKS5_REPLY_COMMAND_NOT_SUPPORTED,
ReplyError::AddressTypeNotSupported => consts::SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED,
}
}
#[inline]
#[rustfmt::skip]
pub fn from_u8(code: u8) -> ReplyError {
match code {
consts::SOCKS5_REPLY_SUCCEEDED => ReplyError::Succeeded,
consts::SOCKS5_REPLY_GENERAL_FAILURE => ReplyError::GeneralFailure,
consts::SOCKS5_REPLY_CONNECTION_NOT_ALLOWED => ReplyError::ConnectionNotAllowed,
consts::SOCKS5_REPLY_NETWORK_UNREACHABLE => ReplyError::NetworkUnreachable,
consts::SOCKS5_REPLY_HOST_UNREACHABLE => ReplyError::HostUnreachable,
consts::SOCKS5_REPLY_CONNECTION_REFUSED => ReplyError::ConnectionRefused,
consts::SOCKS5_REPLY_TTL_EXPIRED => ReplyError::TtlExpired,
consts::SOCKS5_REPLY_COMMAND_NOT_SUPPORTED => ReplyError::CommandNotSupported,
consts::SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED => ReplyError::AddressTypeNotSupported,
_ => unreachable!("ReplyError code unsupported."),
}
}
}
pub fn new_udp_header<T: ToTargetAddr>(target_addr: T) -> Result<Vec<u8>> {
let mut header = vec![
0, 0, 0, ];
header.append(&mut target_addr.to_target_addr()?.to_be_bytes()?);
Ok(header)
}
pub async fn parse_udp_request<'a>(mut req: &'a [u8]) -> Result<(u8, TargetAddr, &'a [u8])> {
let rsv = read_exact!(req, [0u8; 2]).context("Malformed request")?;
if !rsv.eq(&[0u8; 2]) {
return Err(ReplyError::GeneralFailure.into());
}
let [frag, atyp] = read_exact!(req, [0u8; 2]).context("Malformed request")?;
let target_addr = read_address(&mut req, atyp).await.map_err(|e| {
error!("{:#}", e);
ReplyError::AddressTypeNotSupported
})?;
Ok((frag, target_addr, req))
}
#[cfg(test)]
mod test {
use anyhow::Result;
use tokio::{
net::{TcpListener, TcpStream, UdpSocket},
sync::oneshot::Sender,
};
use crate::{
client,
server::{self, SimpleUserPassword},
};
use std::{
net::{SocketAddr, ToSocketAddrs},
num::ParseIntError,
sync::Arc,
};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::oneshot;
use tokio_test::block_on;
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
async fn setup_socks_server(
proxy_addr: &str,
auth: Option<SimpleUserPassword>,
tx: Sender<SocketAddr>,
) -> Result<()> {
let mut config = server::Config::default();
config.set_udp_support(true);
let config = match auth {
None => config,
Some(up) => config.with_authentication(up),
};
let config = Arc::new(config);
let listener = TcpListener::bind(proxy_addr).await?;
tx.send(listener.local_addr()?).unwrap();
loop {
let (stream, _) = listener.accept().await?;
let mut socks5_socket = server::Socks5Socket::new(stream, config.clone());
socks5_socket.set_reply_ip(proxy_addr.parse::<SocketAddr>().unwrap().ip());
socks5_socket.upgrade_to_socks5().await?;
}
}
async fn google(mut socket: TcpStream) -> Result<()> {
socket.write_all(b"GET / HTTP/1.0\r\n\r\n").await?;
let mut result = vec![];
socket.read_to_end(&mut result).await?;
println!("{}", String::from_utf8_lossy(&result));
assert!(result.starts_with(b"HTTP/1.0"));
assert!(result.ends_with(b"</HTML>\r\n") || result.ends_with(b"</html>"));
Ok(())
}
#[test]
fn google_no_auth() {
init();
block_on(async {
let (tx, rx) = oneshot::channel();
tokio::spawn(setup_socks_server("[::1]:0", None, tx));
let socket = client::Socks5Stream::connect(
rx.await.unwrap(),
"google.com".to_owned(),
80,
client::Config::default(),
)
.await
.unwrap();
google(socket.get_socket()).await.unwrap();
});
}
#[test]
fn mock_udp_assosiate_no_auth() {
init();
block_on(async {
const MOCK_ADDRESS: &str = "[::1]:40235";
let (tx, rx) = oneshot::channel();
tokio::spawn(setup_socks_server("[::1]:0", None, tx));
let backing_socket = TcpStream::connect(rx.await.unwrap()).await.unwrap();
let tunnel = client::Socks5Datagram::bind(backing_socket, "[::]:0")
.await
.unwrap();
let mock_udp_server = UdpSocket::bind(MOCK_ADDRESS).await.unwrap();
tunnel
.send_to(
b"hello world!",
MOCK_ADDRESS.to_socket_addrs().unwrap().next().unwrap(),
)
.await
.unwrap();
println!("Send packet to {}", MOCK_ADDRESS);
let mut buf = [0; 13];
let (len, addr) = mock_udp_server.recv_from(&mut buf).await.unwrap();
assert_eq!(len, 12);
assert_eq!(&buf[..12], b"hello world!");
mock_udp_server
.send_to(b"hello world!", addr)
.await
.unwrap();
println!("Recieve packet from {}", MOCK_ADDRESS);
let len = tunnel.recv_from(&mut buf).await.unwrap().0;
assert_eq!(len, 12);
assert_eq!(&buf[..12], b"hello world!");
});
}
#[test]
fn dns_udp_assosiate_no_auth() {
init();
block_on(async {
const DNS_SERVER: &str = "1.1.1.1:53";
let (tx, rx) = oneshot::channel();
tokio::spawn(setup_socks_server("[::1]:0", None, tx));
let backing_socket = TcpStream::connect(rx.await.unwrap()).await.unwrap();
let tunnel = client::Socks5Datagram::bind(backing_socket, "[::]:0")
.await
.unwrap();
#[rustfmt::skip]
tunnel.send_to(
&decode_hex(&(
"AAAA".to_owned() + "0100" + "0001" + "0000" + "0000" + "0000" + "076578616d706c65"+ "03636f6d00" + "0001" + "0001" ))
.unwrap(),
DNS_SERVER.to_socket_addrs().unwrap().next().unwrap(),
).await.unwrap();
println!("Send packet to {}", DNS_SERVER);
let mut buf = [0; 128];
println!("Recieve packet from {}", DNS_SERVER);
tunnel.recv_from(&mut buf).await.unwrap();
println!("dns response {:?}", buf);
#[rustfmt::skip]
assert!(buf.starts_with(&decode_hex(&(
"AAAA".to_owned() + "8180" + "0001" )).unwrap()));
});
}
fn decode_hex(s: &str) -> Result<Vec<u8>, ParseIntError> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16))
.collect()
}
}