use std::net::SocketAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
use tokio::task::JoinHandle;
const SOCKS_VERSION: u8 = 0x05;
const AUTH_NONE: u8 = 0x00;
const AUTH_PASSWORD: u8 = 0x02;
const CMD_CONNECT: u8 = 0x01;
const ATYP_IPV4: u8 = 0x01;
const ATYP_DOMAIN: u8 = 0x03;
const REP_SUCCESS: u8 = 0x00;
const AUTH_SUBNEG_VERSION: u8 = 0x01;
const AUTH_SUBNEG_SUCCESS: u8 = 0x00;
pub struct MockSocks5Server {
addr: SocketAddr,
target_addr: SocketAddr,
listener: Option<TcpListener>,
}
impl MockSocks5Server {
pub async fn new(target_addr: SocketAddr) -> std::io::Result<Self> {
let listener = TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;
Ok(Self {
addr,
target_addr,
listener: Some(listener),
})
}
pub fn addr(&self) -> SocketAddr {
self.addr
}
pub fn spawn(mut self) -> JoinHandle<()> {
let listener = self.listener.take().expect("listener already consumed");
let target_addr = self.target_addr;
tokio::spawn(async move {
let (mut client, _) = listener.accept().await.expect("accept failed");
let mut ver_nmethods = [0u8; 2];
client
.read_exact(&mut ver_nmethods)
.await
.expect("read version+nmethods");
assert_eq!(ver_nmethods[0], SOCKS_VERSION, "expected SOCKS5");
let nmethods = ver_nmethods[1] as usize;
let mut methods = vec![0u8; nmethods];
client.read_exact(&mut methods).await.expect("read methods");
let selected = if methods.contains(&AUTH_PASSWORD) {
AUTH_PASSWORD
} else if methods.contains(&AUTH_NONE) {
AUTH_NONE
} else {
panic!("no supported auth method offered");
};
client
.write_all(&[SOCKS_VERSION, selected])
.await
.expect("write method reply");
if selected == AUTH_PASSWORD {
let mut subneg_header = [0u8; 2];
client
.read_exact(&mut subneg_header)
.await
.expect("read subneg header");
assert_eq!(
subneg_header[0], AUTH_SUBNEG_VERSION,
"expected auth subneg v1"
);
let ulen = subneg_header[1] as usize;
let mut uname = vec![0u8; ulen];
client.read_exact(&mut uname).await.expect("read username");
let mut plen_buf = [0u8; 1];
client.read_exact(&mut plen_buf).await.expect("read plen");
let plen = plen_buf[0] as usize;
let mut passwd = vec![0u8; plen];
client.read_exact(&mut passwd).await.expect("read password");
client
.write_all(&[AUTH_SUBNEG_VERSION, AUTH_SUBNEG_SUCCESS])
.await
.expect("write subneg reply");
}
let mut header = [0u8; 4];
client
.read_exact(&mut header)
.await
.expect("read connect header");
assert_eq!(header[0], SOCKS_VERSION);
assert_eq!(header[1], CMD_CONNECT);
match header[3] {
ATYP_IPV4 => {
let mut addr_port = [0u8; 6]; client
.read_exact(&mut addr_port)
.await
.expect("read IPv4 addr");
}
ATYP_DOMAIN => {
let mut len_buf = [0u8; 1];
client
.read_exact(&mut len_buf)
.await
.expect("read domain len");
let domain_len = len_buf[0] as usize;
let mut domain_port = vec![0u8; domain_len + 2]; client
.read_exact(&mut domain_port)
.await
.expect("read domain addr");
}
other => panic!("unsupported ATYP: {}", other),
}
let mut target = tokio::net::TcpStream::connect(target_addr)
.await
.expect("connect to target");
let reply = [
SOCKS_VERSION,
REP_SUCCESS,
0x00, ATYP_IPV4,
0,
0,
0,
0, 0,
0, ];
client.write_all(&reply).await.expect("write connect reply");
let _ = tokio::io::copy_bidirectional(&mut client, &mut target).await;
})
}
}