Implement SOCKS5 authentication

This commit is contained in:
B. Blechschmidt 2023-03-22 01:02:27 +01:00
parent c1aaec6159
commit d2aef08e3c
5 changed files with 244 additions and 121 deletions

View file

@ -1,6 +1,6 @@
use crate::tun2proxy::{ use crate::tun2proxy::{
Connection, ConnectionManager, IncomingDataEvent, IncomingDirection, OutgoingDataEvent, Connection, ConnectionManager, Credentials, IncomingDataEvent, IncomingDirection,
OutgoingDirection, ProxyError, TcpProxy, OutgoingDataEvent, OutgoingDirection, ProxyError, TcpProxy,
}; };
use std::collections::VecDeque; use std::collections::VecDeque;
use std::net::SocketAddr; use std::net::SocketAddr;
@ -156,6 +156,7 @@ impl TcpProxy for HttpConnection {
pub struct HttpManager { pub struct HttpManager {
server: std::net::SocketAddr, server: std::net::SocketAddr,
credentials: Credentials,
} }
impl ConnectionManager for HttpManager { impl ConnectionManager for HttpManager {
@ -163,7 +164,11 @@ impl ConnectionManager for HttpManager {
connection.proto == smoltcp::wire::IpProtocol::Tcp.into() connection.proto == smoltcp::wire::IpProtocol::Tcp.into()
} }
fn new_connection(&self, connection: &Connection) -> Option<std::boxed::Box<dyn TcpProxy>> { fn new_connection(
&self,
connection: &Connection,
_manager: std::rc::Rc<dyn ConnectionManager>,
) -> Option<std::boxed::Box<dyn TcpProxy>> {
if connection.proto != smoltcp::wire::IpProtocol::Tcp.into() { if connection.proto != smoltcp::wire::IpProtocol::Tcp.into() {
return None; return None;
} }
@ -175,10 +180,17 @@ impl ConnectionManager for HttpManager {
fn get_server(&self) -> SocketAddr { fn get_server(&self) -> SocketAddr {
self.server self.server
} }
fn get_credentials(&self) -> &Credentials {
&self.credentials
}
} }
impl HttpManager { impl HttpManager {
pub fn new(server: SocketAddr) -> Self { pub fn new(server: SocketAddr, credentials: Credentials) -> std::rc::Rc<Self> {
Self { server } std::rc::Rc::new(Self {
server,
credentials,
})
} }
} }

View file

@ -1,3 +1,4 @@
use crate::tun2proxy::Credentials;
use crate::{http::HttpManager, socks5::Socks5Manager, tun2proxy::TunToProxy}; use crate::{http::HttpManager, socks5::Socks5Manager, tun2proxy::TunToProxy};
use std::net::SocketAddr; use std::net::SocketAddr;
@ -12,14 +13,14 @@ pub enum ProxyType {
Http, Http,
} }
pub fn main_entry(tun: &str, addr: SocketAddr, proxy_type: ProxyType) { pub fn main_entry(tun: &str, addr: SocketAddr, proxy_type: ProxyType, credentials: Credentials) {
let mut ttp = TunToProxy::new(tun); let mut ttp = TunToProxy::new(tun);
match proxy_type { match proxy_type {
ProxyType::Socks5 => { ProxyType::Socks5 => {
ttp.add_connection_manager(Box::new(Socks5Manager::new(addr))); ttp.add_connection_manager(Socks5Manager::new(addr, credentials));
} }
ProxyType::Http => { ProxyType::Http => {
ttp.add_connection_manager(Box::new(HttpManager::new(addr))); ttp.add_connection_manager(HttpManager::new(addr, credentials));
} }
} }
ttp.run(); ttp.run();

View file

@ -1,6 +1,9 @@
use std::net::SocketAddr;
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use env_logger::Env; use env_logger::Env;
use std::net::SocketAddr;
use tun2proxy::tun2proxy::Credentials;
use tun2proxy::{main_entry, ProxyType}; use tun2proxy::{main_entry, ProxyType};
/// Tunnel interface to proxy /// Tunnel interface to proxy
@ -18,6 +21,14 @@ struct Args {
/// Server address with format ip:port /// Server address with format ip:port
#[clap(short, long, value_name = "ip:port")] #[clap(short, long, value_name = "ip:port")]
addr: SocketAddr, addr: SocketAddr,
/// Username for authentication
#[clap(long, value_name = "username")]
username: Option<String>,
/// Username for authentication
#[clap(long, value_name = "password")]
password: Option<String>,
} }
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
@ -32,14 +43,23 @@ fn main() {
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
let args = Args::parse(); let args = Args::parse();
let credentials = if args.username.is_some() || args.password.is_some() {
Credentials::new(
args.username.unwrap_or(String::from("")),
args.password.unwrap_or(String::from("")),
)
} else {
Credentials::none()
};
match args.proxy_type { match args.proxy_type {
ArgProxyType::Socks5 => { ArgProxyType::Socks5 => {
log::info!("SOCKS5 server: {}", args.addr); log::info!("SOCKS5 server: {}", args.addr);
main_entry(&args.tun, args.addr, ProxyType::Socks5); main_entry(&args.tun, args.addr, ProxyType::Socks5, credentials);
} }
ArgProxyType::Http => { ArgProxyType::Http => {
log::info!("HTTP server: {}", args.addr); log::info!("HTTP server: {}", args.addr);
main_entry(&args.tun, args.addr, ProxyType::Http); main_entry(&args.tun, args.addr, ProxyType::Http, credentials);
} }
} }
} }

View file

@ -1,6 +1,6 @@
use crate::tun2proxy::{ use crate::tun2proxy::{
Connection, ConnectionManager, IncomingDataEvent, IncomingDirection, OutgoingDataEvent, Connection, ConnectionManager, Credentials, IncomingDataEvent, IncomingDirection,
OutgoingDirection, ProxyError, TcpProxy, OutgoingDataEvent, OutgoingDirection, ProxyError, TcpProxy,
}; };
use std::collections::VecDeque; use std::collections::VecDeque;
use std::net::{IpAddr, SocketAddr}; use std::net::{IpAddr, SocketAddr};
@ -10,6 +10,8 @@ use std::net::{IpAddr, SocketAddr};
enum SocksState { enum SocksState {
ClientHello, ClientHello,
ServerHello, ServerHello,
SendAuthData,
ReceiveAuthResponse,
SendRequest, SendRequest,
ReceiveResponse, ReceiveResponse,
Established, Established,
@ -51,7 +53,7 @@ impl std::fmt::Display for SocksReplies {
} }
} }
pub struct SocksConnection { pub(crate) struct SocksConnection {
connection: Connection, connection: Connection,
state: SocksState, state: SocksState,
client_inbuf: VecDeque<u8>, client_inbuf: VecDeque<u8>,
@ -59,10 +61,11 @@ pub struct SocksConnection {
client_outbuf: VecDeque<u8>, client_outbuf: VecDeque<u8>,
server_outbuf: VecDeque<u8>, server_outbuf: VecDeque<u8>,
data_buf: VecDeque<u8>, data_buf: VecDeque<u8>,
manager: std::rc::Rc<dyn ConnectionManager>,
} }
impl SocksConnection { impl SocksConnection {
pub fn new(connection: &Connection) -> Self { pub fn new(connection: &Connection, manager: std::rc::Rc<dyn ConnectionManager>) -> Self {
let mut result = Self { let mut result = Self {
connection: *connection, connection: *connection,
state: SocksState::ServerHello, state: SocksState::ServerHello,
@ -71,24 +74,35 @@ impl SocksConnection {
client_outbuf: Default::default(), client_outbuf: Default::default(),
server_outbuf: Default::default(), server_outbuf: Default::default(),
data_buf: Default::default(), data_buf: Default::default(),
manager,
}; };
result.server_outbuf.extend(&[5u8, 1, 0]); result.send_client_hello();
result.state = SocksState::ServerHello;
result result
} }
pub fn state_change(&mut self) -> Result<(), ProxyError> { fn send_client_hello(&mut self) {
let dst_ip = self.connection.dst.ip(); let credentials = self.manager.get_credentials();
if credentials.authenticate {
self.server_outbuf.extend(&[5u8, 1, 2]);
} else {
self.server_outbuf.extend(&[5u8, 1, 0]);
}
self.state = SocksState::ServerHello;
}
match self.state { fn receive_server_hello(&mut self) -> Result<(), ProxyError> {
SocksState::ServerHello if self.server_inbuf.len() >= 2 => { if self.server_inbuf.len() < 2 {
return Ok(());
}
if self.server_inbuf[0] != 5 { if self.server_inbuf[0] != 5 {
return Err(ProxyError::new( return Err(ProxyError::new(
"SOCKS server replied with an unexpected version.".into(), "SOCKS server replied with an unexpected version.".into(),
)); ));
} }
if self.server_inbuf[1] != 0 { if self.server_inbuf[1] != 0 && !self.manager.get_credentials().authenticate
|| self.server_inbuf[1] != 2 && self.manager.get_credentials().authenticate
{
return Err(ProxyError::new( return Err(ProxyError::new(
"SOCKS server requires an unsupported authentication method.".into(), "SOCKS server requires an unsupported authentication method.".into(),
)); ));
@ -96,22 +110,42 @@ impl SocksConnection {
self.server_inbuf.drain(0..2); self.server_inbuf.drain(0..2);
let cmd = if dst_ip.is_ipv4() { 1 } else { 4 }; if self.manager.get_credentials().authenticate {
self.server_outbuf.extend(&[5u8, 1, 0, cmd]); self.state = SocksState::SendAuthData;
match dst_ip { } else {
IpAddr::V4(ip) => self.server_outbuf.extend(ip.octets().as_ref()), self.state = SocksState::SendRequest;
IpAddr::V6(ip) => self.server_outbuf.extend(ip.octets().as_ref()), }
}; self.state_change()
self.server_outbuf.extend(&[
(self.connection.dst.port() >> 8) as u8,
(self.connection.dst.port() & 0xff) as u8,
]);
self.state = SocksState::ReceiveResponse;
return self.state_change();
} }
SocksState::ReceiveResponse if self.server_inbuf.len() >= 4 => { fn send_auth_data(&mut self) -> Result<(), ProxyError> {
let credentials = self.manager.get_credentials();
self.server_outbuf
.extend(&[1u8, credentials.username.len() as u8]);
self.server_outbuf.extend(&credentials.username);
self.server_outbuf
.extend(&[credentials.password.len() as u8]);
self.server_outbuf.extend(&credentials.password);
self.state = SocksState::ReceiveAuthResponse;
self.state_change()
}
fn receive_auth_data(&mut self) -> Result<(), ProxyError> {
if self.server_inbuf.len() < 2 {
return Ok(());
}
if self.server_inbuf[0] != 1 || self.server_inbuf[1] != 0 {
return Err(ProxyError::new("SOCKS authentication failed.".into()));
}
self.server_inbuf.drain(0..2);
self.state = SocksState::SendRequest;
self.state_change()
}
fn receive_connection_status(&mut self) -> Result<(), ProxyError> {
if self.server_inbuf.len() < 4 {
return Ok(());
}
let ver = self.server_inbuf[0]; let ver = self.server_inbuf[0];
let rep = self.server_inbuf[1]; let rep = self.server_inbuf[1];
let _rsv = self.server_inbuf[2]; let _rsv = self.server_inbuf[2];
@ -159,19 +193,47 @@ impl SocksConnection {
self.data_buf.clear(); self.data_buf.clear();
self.state = SocksState::Established; self.state = SocksState::Established;
return self.state_change(); self.state_change()
} }
fn send_request(&mut self) -> Result<(), ProxyError> {
let dst_ip = self.connection.dst.ip();
let cmd = if dst_ip.is_ipv4() { 1 } else { 4 };
self.server_outbuf.extend(&[5u8, 1, 0, cmd]);
match dst_ip {
IpAddr::V4(ip) => self.server_outbuf.extend(ip.octets().as_ref()),
IpAddr::V6(ip) => self.server_outbuf.extend(ip.octets().as_ref()),
};
self.server_outbuf.extend(&[
(self.connection.dst.port() >> 8) as u8,
(self.connection.dst.port() & 0xff) as u8,
]);
self.state = SocksState::ReceiveResponse;
self.state_change()
}
pub fn state_change(&mut self) -> Result<(), ProxyError> {
match self.state {
SocksState::ServerHello => self.receive_server_hello(),
SocksState::SendAuthData => self.send_auth_data(),
SocksState::ReceiveAuthResponse => self.receive_auth_data(),
SocksState::SendRequest => self.send_request(),
SocksState::ReceiveResponse => self.receive_connection_status(),
SocksState::Established => { SocksState::Established => {
self.client_outbuf.extend(self.server_inbuf.iter()); self.client_outbuf.extend(self.server_inbuf.iter());
self.server_outbuf.extend(self.client_inbuf.iter()); self.server_outbuf.extend(self.client_inbuf.iter());
self.server_inbuf.clear(); self.server_inbuf.clear();
self.client_inbuf.clear(); self.client_inbuf.clear();
Ok(())
} }
_ => {} _ => Ok(()),
} }
Ok(())
} }
} }
@ -223,9 +285,7 @@ impl TcpProxy for SocksConnection {
pub struct Socks5Manager { pub struct Socks5Manager {
server: std::net::SocketAddr, server: std::net::SocketAddr,
authentication: SocksAuthentication, credentials: Credentials,
username: Vec<u8>,
password: Vec<u8>,
} }
impl ConnectionManager for Socks5Manager { impl ConnectionManager for Socks5Manager {
@ -233,11 +293,17 @@ impl ConnectionManager for Socks5Manager {
connection.proto == smoltcp::wire::IpProtocol::Tcp.into() connection.proto == smoltcp::wire::IpProtocol::Tcp.into()
} }
fn new_connection(&self, connection: &Connection) -> Option<std::boxed::Box<dyn TcpProxy>> { fn new_connection(
&self,
connection: &Connection,
manager: std::rc::Rc<dyn ConnectionManager>,
) -> Option<std::boxed::Box<dyn TcpProxy>> {
if connection.proto != smoltcp::wire::IpProtocol::Tcp.into() { if connection.proto != smoltcp::wire::IpProtocol::Tcp.into() {
return None; return None;
} }
Some(std::boxed::Box::new(SocksConnection::new(connection))) Some(std::boxed::Box::new(SocksConnection::new(
connection, manager,
)))
} }
fn close_connection(&self, _: &Connection) {} fn close_connection(&self, _: &Connection) {}
@ -245,23 +311,17 @@ impl ConnectionManager for Socks5Manager {
fn get_server(&self) -> SocketAddr { fn get_server(&self) -> SocketAddr {
self.server self.server
} }
fn get_credentials(&self) -> &Credentials {
&self.credentials
}
} }
impl Socks5Manager { impl Socks5Manager {
pub fn new(server: SocketAddr) -> Self { pub fn new(server: SocketAddr, credentials: Credentials) -> std::rc::Rc<Self> {
Self { std::rc::Rc::new(Self {
server, server,
authentication: SocksAuthentication::None, credentials,
username: Default::default(), })
password: Default::default(),
}
}
#[allow(dead_code)]
pub fn set_credentials(&mut self, username: &[u8], password: &[u8]) {
assert!(username.len() <= 255 && password.len() <= 255);
self.authentication = SocksAuthentication::Password;
self.username = Vec::from(username);
self.password = Vec::from(password);
} }
} }

View file

@ -162,6 +162,27 @@ struct ConnectionState {
handler: std::boxed::Box<dyn TcpProxy>, handler: std::boxed::Box<dyn TcpProxy>,
} }
#[derive(Default, Clone)]
pub struct Credentials {
pub(crate) authenticate: bool,
pub(crate) username: Vec<u8>,
pub(crate) password: Vec<u8>,
}
impl Credentials {
pub fn new(username: String, password: String) -> Self {
Self {
authenticate: true,
username: username.as_bytes().to_vec(),
password: password.as_bytes().to_vec(),
}
}
pub fn none() -> Self {
Default::default()
}
}
pub(crate) trait TcpProxy { pub(crate) trait TcpProxy {
fn push_data(&mut self, event: IncomingDataEvent<'_>) -> Result<(), ProxyError>; fn push_data(&mut self, event: IncomingDataEvent<'_>) -> Result<(), ProxyError>;
fn consume_data(&mut self, dir: OutgoingDirection, size: usize); fn consume_data(&mut self, dir: OutgoingDirection, size: usize);
@ -171,9 +192,14 @@ pub(crate) trait TcpProxy {
pub(crate) trait ConnectionManager { pub(crate) trait ConnectionManager {
fn handles_connection(&self, connection: &Connection) -> bool; fn handles_connection(&self, connection: &Connection) -> bool;
fn new_connection(&self, connection: &Connection) -> Option<std::boxed::Box<dyn TcpProxy>>; fn new_connection(
&self,
connection: &Connection,
manager: std::rc::Rc<dyn ConnectionManager>,
) -> Option<std::boxed::Box<dyn TcpProxy>>;
fn close_connection(&self, connection: &Connection); fn close_connection(&self, connection: &Connection);
fn get_server(&self) -> SocketAddr; fn get_server(&self) -> SocketAddr;
fn get_credentials(&self) -> &Credentials;
} }
pub(crate) struct TunToProxy<'a> { pub(crate) struct TunToProxy<'a> {
@ -183,7 +209,7 @@ pub(crate) struct TunToProxy<'a> {
udp_token: Token, udp_token: Token,
iface: Interface, iface: Interface,
connections: HashMap<Connection, ConnectionState>, connections: HashMap<Connection, ConnectionState>,
connection_managers: Vec<std::boxed::Box<dyn ConnectionManager>>, connection_managers: Vec<std::rc::Rc<dyn ConnectionManager>>,
next_token: usize, next_token: usize,
token_to_connection: HashMap<Token, Connection>, token_to_connection: HashMap<Token, Connection>,
sockets: SocketSet<'a>, sockets: SocketSet<'a>,
@ -239,7 +265,7 @@ impl<'a> TunToProxy<'a> {
} }
} }
pub(crate) fn add_connection_manager(&mut self, manager: Box<dyn ConnectionManager>) { pub(crate) fn add_connection_manager(&mut self, manager: std::rc::Rc<dyn ConnectionManager>) {
self.connection_managers.push(manager); self.connection_managers.push(manager);
} }
@ -270,10 +296,13 @@ impl<'a> TunToProxy<'a> {
info!("CLOSE {}", connection); info!("CLOSE {}", connection);
} }
fn get_connection_manager(&self, connection: &Connection) -> Option<&dyn ConnectionManager> { fn get_connection_manager(
&self,
connection: &Connection,
) -> Option<std::rc::Rc<dyn ConnectionManager>> {
for manager in self.connection_managers.iter() { for manager in self.connection_managers.iter() {
if manager.handles_connection(connection) { if manager.handles_connection(connection) {
return Some(manager.as_ref()); return Some(manager.clone());
} }
} }
None None
@ -335,7 +364,8 @@ impl<'a> TunToProxy<'a> {
let server = cm.unwrap().get_server(); let server = cm.unwrap().get_server();
if first_packet { if first_packet {
for manager in self.connection_managers.iter_mut() { for manager in self.connection_managers.iter_mut() {
if let Some(handler) = manager.new_connection(&connection) { if let Some(handler) = manager.new_connection(&connection, manager.clone())
{
let mut socket = smoltcp::socket::tcp::Socket::new( let mut socket = smoltcp::socket::tcp::Socket::new(
smoltcp::socket::tcp::SocketBuffer::new(vec![0; 4096]), smoltcp::socket::tcp::SocketBuffer::new(vec![0; 4096]),
smoltcp::socket::tcp::SocketBuffer::new(vec![0; 4096]), smoltcp::socket::tcp::SocketBuffer::new(vec![0; 4096]),