mirror of
https://github.com/tun2proxy/tun2proxy.git
synced 2025-06-05 14:20:21 +00:00
Implement SOCKS4
This commit is contained in:
parent
7dec7f59f1
commit
341bab5586
6 changed files with 175 additions and 43 deletions
|
@ -178,11 +178,11 @@ impl ConnectionManager for HttpManager {
|
|||
&self,
|
||||
connection: &Connection,
|
||||
manager: Rc<dyn ConnectionManager>,
|
||||
) -> Option<Box<dyn TcpProxy>> {
|
||||
) -> Result<Option<Box<dyn TcpProxy>>, Error> {
|
||||
if connection.proto != IpProtocol::Tcp.into() {
|
||||
return None;
|
||||
return Ok(None);
|
||||
}
|
||||
Some(Box::new(HttpConnection::new(connection, manager)))
|
||||
Ok(Some(Box::new(HttpConnection::new(connection, manager))))
|
||||
}
|
||||
|
||||
fn close_connection(&self, _: &Connection) {}
|
||||
|
|
19
src/lib.rs
19
src/lib.rs
|
@ -1,6 +1,7 @@
|
|||
use crate::error::{s2e, Error};
|
||||
use crate::socks5::SocksVersion;
|
||||
use crate::tun2proxy::{Credentials, Options};
|
||||
use crate::{http::HttpManager, socks5::Socks5Manager, tun2proxy::TunToProxy};
|
||||
use crate::{http::HttpManager, socks5::SocksManager, tun2proxy::TunToProxy};
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
|
||||
pub mod error;
|
||||
|
@ -47,6 +48,7 @@ impl Proxy {
|
|||
let scheme = url.scheme();
|
||||
|
||||
let proxy_type = match url.scheme().to_ascii_lowercase().as_str() {
|
||||
"socks4" => Some(ProxyType::Socks4),
|
||||
"socks5" => Some(ProxyType::Socks5),
|
||||
"http" => Some(ProxyType::Http),
|
||||
_ => None,
|
||||
|
@ -63,6 +65,7 @@ impl Proxy {
|
|||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub enum ProxyType {
|
||||
Socks4,
|
||||
Socks5,
|
||||
Http,
|
||||
}
|
||||
|
@ -70,6 +73,7 @@ pub enum ProxyType {
|
|||
impl std::fmt::Display for ProxyType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ProxyType::Socks4 => write!(f, "socks4"),
|
||||
ProxyType::Socks5 => write!(f, "socks5"),
|
||||
ProxyType::Http => write!(f, "http"),
|
||||
}
|
||||
|
@ -79,8 +83,19 @@ impl std::fmt::Display for ProxyType {
|
|||
pub fn main_entry(tun: &str, proxy: Proxy, options: Options) {
|
||||
let mut ttp = TunToProxy::new(tun, options);
|
||||
match proxy.proxy_type {
|
||||
ProxyType::Socks4 => {
|
||||
ttp.add_connection_manager(SocksManager::new(
|
||||
proxy.addr,
|
||||
SocksVersion::V4,
|
||||
proxy.credentials,
|
||||
));
|
||||
}
|
||||
ProxyType::Socks5 => {
|
||||
ttp.add_connection_manager(Socks5Manager::new(proxy.addr, proxy.credentials));
|
||||
ttp.add_connection_manager(SocksManager::new(
|
||||
proxy.addr,
|
||||
SocksVersion::V5,
|
||||
proxy.credentials,
|
||||
));
|
||||
}
|
||||
ProxyType::Http => {
|
||||
ttp.add_connection_manager(HttpManager::new(proxy.addr, proxy.credentials));
|
||||
|
|
147
src/socks5.rs
147
src/socks5.rs
|
@ -28,6 +28,12 @@ enum SocksAddressType {
|
|||
Ipv6 = 4,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum SocksVersion {
|
||||
V4 = 4,
|
||||
V5 = 5,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum SocksAuthentication {
|
||||
None = 0,
|
||||
|
@ -63,10 +69,15 @@ pub(crate) struct SocksConnection {
|
|||
server_outbuf: VecDeque<u8>,
|
||||
data_buf: VecDeque<u8>,
|
||||
manager: Rc<dyn ConnectionManager>,
|
||||
version: SocksVersion,
|
||||
}
|
||||
|
||||
impl SocksConnection {
|
||||
pub fn new(connection: &Connection, manager: Rc<dyn ConnectionManager>) -> Self {
|
||||
pub fn new(
|
||||
connection: &Connection,
|
||||
manager: Rc<dyn ConnectionManager>,
|
||||
version: SocksVersion,
|
||||
) -> Result<Self, Error> {
|
||||
let mut result = Self {
|
||||
connection: connection.clone(),
|
||||
state: SocksState::ServerHello,
|
||||
|
@ -76,35 +87,92 @@ impl SocksConnection {
|
|||
server_outbuf: Default::default(),
|
||||
data_buf: Default::default(),
|
||||
manager,
|
||||
version,
|
||||
};
|
||||
result.send_client_hello();
|
||||
result
|
||||
result.send_client_hello()?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn send_client_hello(&mut self) {
|
||||
fn send_client_hello(&mut self) -> Result<(), Error> {
|
||||
let credentials = self.manager.get_credentials();
|
||||
if credentials.is_some() {
|
||||
self.server_outbuf
|
||||
.extend(&[5u8, 1, SocksAuthentication::Password as u8]);
|
||||
} else {
|
||||
self.server_outbuf
|
||||
.extend(&[5u8, 1, SocksAuthentication::None as u8]);
|
||||
match self.version {
|
||||
SocksVersion::V4 => {
|
||||
self.server_outbuf.extend(&[
|
||||
4u8,
|
||||
1,
|
||||
(self.connection.dst.port >> 8) as u8,
|
||||
(self.connection.dst.port & 0xff) as u8,
|
||||
]);
|
||||
let mut ip_vec = Vec::<u8>::new();
|
||||
let mut name_vec = Vec::<u8>::new();
|
||||
match &self.connection.dst.host {
|
||||
DestinationHost::Address(dst_ip) => {
|
||||
match dst_ip {
|
||||
IpAddr::V4(ip) => ip_vec.extend(ip.octets().as_ref()),
|
||||
IpAddr::V6(_) => return Err("SOCKS4 does not support IPv6".into()),
|
||||
};
|
||||
}
|
||||
DestinationHost::Hostname(host) => {
|
||||
ip_vec.extend(&[0, 0, 0, host.len() as u8]);
|
||||
name_vec.extend(host.as_bytes());
|
||||
name_vec.push(0);
|
||||
}
|
||||
}
|
||||
self.server_outbuf.extend(ip_vec);
|
||||
if let Some(credentials) = credentials {
|
||||
self.server_outbuf.extend(&credentials.username);
|
||||
if !credentials.password.is_empty() {
|
||||
self.server_outbuf.push_back(b':');
|
||||
self.server_outbuf.extend(&credentials.password);
|
||||
}
|
||||
}
|
||||
self.server_outbuf.push_back(0);
|
||||
self.server_outbuf.extend(name_vec);
|
||||
}
|
||||
|
||||
SocksVersion::V5 => {
|
||||
if credentials.is_some() {
|
||||
self.server_outbuf
|
||||
.extend(&[5u8, 1, SocksAuthentication::Password as u8]);
|
||||
} else {
|
||||
self.server_outbuf
|
||||
.extend(&[5u8, 1, SocksAuthentication::None as u8]);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.state = SocksState::ServerHello;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn receive_server_hello(&mut self) -> Result<(), Error> {
|
||||
fn receive_server_hello_socks4(&mut self) -> Result<(), Error> {
|
||||
if self.server_inbuf.len() < 8 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.server_inbuf[1] != 0x5a {
|
||||
return Err("SOCKS4 server replied with an unexpected reply code.".into());
|
||||
}
|
||||
|
||||
self.server_inbuf.drain(0..8);
|
||||
self.server_outbuf.append(&mut self.data_buf);
|
||||
self.data_buf.clear();
|
||||
|
||||
self.state = SocksState::Established;
|
||||
self.state_change()
|
||||
}
|
||||
|
||||
fn receive_server_hello_socks5(&mut self) -> Result<(), Error> {
|
||||
if self.server_inbuf.len() < 2 {
|
||||
return Ok(());
|
||||
}
|
||||
if self.server_inbuf[0] != 5 {
|
||||
return Err("SOCKS server replied with an unexpected version.".into());
|
||||
return Err("SOCKS5 server replied with an unexpected version.".into());
|
||||
}
|
||||
|
||||
if self.server_inbuf[1] != 0 && self.manager.get_credentials().is_none()
|
||||
|| self.server_inbuf[1] != 2 && self.manager.get_credentials().is_some()
|
||||
{
|
||||
return Err("SOCKS server requires an unsupported authentication method.".into());
|
||||
return Err("SOCKS5 server requires an unsupported authentication method.".into());
|
||||
}
|
||||
|
||||
self.server_inbuf.drain(0..2);
|
||||
|
@ -117,6 +185,13 @@ impl SocksConnection {
|
|||
self.state_change()
|
||||
}
|
||||
|
||||
fn receive_server_hello(&mut self) -> Result<(), Error> {
|
||||
match self.version {
|
||||
SocksVersion::V4 => self.receive_server_hello_socks4(),
|
||||
SocksVersion::V5 => self.receive_server_hello_socks5(),
|
||||
}
|
||||
}
|
||||
|
||||
fn send_auth_data(&mut self) -> Result<(), Error> {
|
||||
let tmp = Credentials::default();
|
||||
let credentials = self.manager.get_credentials().as_ref().unwrap_or(&tmp);
|
||||
|
@ -152,18 +227,18 @@ impl SocksConnection {
|
|||
let atyp = self.server_inbuf[3];
|
||||
|
||||
if ver != 5 {
|
||||
return Err("SOCKS server replied with an unexpected version.".into());
|
||||
return Err("SOCKS5 server replied with an unexpected version.".into());
|
||||
}
|
||||
|
||||
if rep != 0 {
|
||||
return Err("SOCKS connection unsuccessful.".into());
|
||||
return Err("SOCKS5 connection unsuccessful.".into());
|
||||
}
|
||||
|
||||
if atyp != SocksAddressType::Ipv4 as u8
|
||||
&& atyp != SocksAddressType::Ipv6 as u8
|
||||
&& atyp != SocksAddressType::DomainName as u8
|
||||
{
|
||||
return Err("SOCKS server replied with unrecognized address type.".into());
|
||||
return Err("SOCKS5 server replied with unrecognized address type.".into());
|
||||
}
|
||||
|
||||
if atyp == SocksAddressType::DomainName as u8 && self.server_inbuf.len() < 5 {
|
||||
|
@ -221,6 +296,14 @@ impl SocksConnection {
|
|||
self.state_change()
|
||||
}
|
||||
|
||||
fn relay_traffic(&mut self) -> Result<(), Error> {
|
||||
self.client_outbuf.extend(self.server_inbuf.iter());
|
||||
self.server_outbuf.extend(self.client_inbuf.iter());
|
||||
self.server_inbuf.clear();
|
||||
self.client_inbuf.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn state_change(&mut self) -> Result<(), Error> {
|
||||
match self.state {
|
||||
SocksState::ServerHello => self.receive_server_hello(),
|
||||
|
@ -233,13 +316,7 @@ impl SocksConnection {
|
|||
|
||||
SocksState::ReceiveResponse => self.receive_connection_status(),
|
||||
|
||||
SocksState::Established => {
|
||||
self.client_outbuf.extend(self.server_inbuf.iter());
|
||||
self.server_outbuf.extend(self.client_inbuf.iter());
|
||||
self.server_inbuf.clear();
|
||||
self.client_inbuf.clear();
|
||||
Ok(())
|
||||
}
|
||||
SocksState::Established => self.relay_traffic(),
|
||||
|
||||
_ => Ok(()),
|
||||
}
|
||||
|
@ -292,12 +369,13 @@ impl TcpProxy for SocksConnection {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Socks5Manager {
|
||||
pub struct SocksManager {
|
||||
server: SocketAddr,
|
||||
credentials: Option<Credentials>,
|
||||
version: SocksVersion,
|
||||
}
|
||||
|
||||
impl ConnectionManager for Socks5Manager {
|
||||
impl ConnectionManager for SocksManager {
|
||||
fn handles_connection(&self, connection: &Connection) -> bool {
|
||||
connection.proto == IpProtocol::Tcp.into()
|
||||
}
|
||||
|
@ -306,11 +384,15 @@ impl ConnectionManager for Socks5Manager {
|
|||
&self,
|
||||
connection: &Connection,
|
||||
manager: Rc<dyn ConnectionManager>,
|
||||
) -> Option<Box<dyn TcpProxy>> {
|
||||
) -> Result<Option<Box<dyn TcpProxy>>, Error> {
|
||||
if connection.proto != IpProtocol::Tcp.into() {
|
||||
return None;
|
||||
return Ok(None);
|
||||
}
|
||||
Some(Box::new(SocksConnection::new(connection, manager)))
|
||||
Ok(Some(Box::new(SocksConnection::new(
|
||||
connection,
|
||||
manager,
|
||||
self.version,
|
||||
)?)))
|
||||
}
|
||||
|
||||
fn close_connection(&self, _: &Connection) {}
|
||||
|
@ -324,11 +406,16 @@ impl ConnectionManager for Socks5Manager {
|
|||
}
|
||||
}
|
||||
|
||||
impl Socks5Manager {
|
||||
pub fn new(server: SocketAddr, credentials: Option<Credentials>) -> Rc<Self> {
|
||||
impl SocksManager {
|
||||
pub fn new(
|
||||
server: SocketAddr,
|
||||
version: SocksVersion,
|
||||
credentials: Option<Credentials>,
|
||||
) -> Rc<Self> {
|
||||
Rc::new(Self {
|
||||
server,
|
||||
credentials,
|
||||
version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -247,7 +247,7 @@ pub(crate) trait ConnectionManager {
|
|||
&self,
|
||||
connection: &Connection,
|
||||
manager: Rc<dyn ConnectionManager>,
|
||||
) -> Option<Box<dyn TcpProxy>>;
|
||||
) -> Result<Option<Box<dyn TcpProxy>>, Error>;
|
||||
fn close_connection(&self, connection: &Connection);
|
||||
fn get_server(&self) -> SocketAddr;
|
||||
fn get_credentials(&self) -> &Option<Credentials>;
|
||||
|
@ -441,8 +441,9 @@ impl<'a> TunToProxy<'a> {
|
|||
let server = cm.unwrap().get_server();
|
||||
if first_packet {
|
||||
for manager in self.connection_managers.iter_mut() {
|
||||
if let Some(handler) =
|
||||
manager.new_connection(&resolved_conn, manager.clone())
|
||||
if let Some(handler) = manager
|
||||
.new_connection(&resolved_conn, manager.clone())
|
||||
.unwrap()
|
||||
{
|
||||
let mut socket = tcp::Socket::new(
|
||||
tcp::SocketBuffer::new(vec![0; 4096]),
|
||||
|
|
|
@ -205,7 +205,7 @@ impl VirtualDns {
|
|||
}
|
||||
}
|
||||
|
||||
/// Parse a DNS qname at a specific offset and return the name along with its size.
|
||||
/// Parse a non-root DNS qname at a specific offset and return the name along with its size.
|
||||
/// DNS packet parsing should be continued after the name.
|
||||
fn parse_qname(data: &[u8], mut offset: usize) -> Option<(String, usize)> {
|
||||
// Since we only parse qnames and qnames can't point anywhere,
|
||||
|
@ -222,9 +222,15 @@ impl VirtualDns {
|
|||
}
|
||||
let label_len = data[offset];
|
||||
if label_len == 0 {
|
||||
if qname.is_empty() {
|
||||
return None;
|
||||
}
|
||||
offset += 1;
|
||||
break;
|
||||
}
|
||||
if !qname.is_empty() {
|
||||
qname.push('.');
|
||||
}
|
||||
for _ in 0..label_len {
|
||||
offset += 1;
|
||||
if offset >= data.len() {
|
||||
|
@ -232,7 +238,6 @@ impl VirtualDns {
|
|||
}
|
||||
qname.push(data[offset] as char);
|
||||
}
|
||||
qname.push('.');
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,8 +35,12 @@ mod tests {
|
|||
Ok(Test { proxy })
|
||||
}
|
||||
|
||||
fn tests() -> [Result<Test, String>; 2] {
|
||||
[test_from_env("SOCKS5_SERVER"), test_from_env("HTTP_SERVER")]
|
||||
fn tests() -> [Result<Test, String>; 3] {
|
||||
[
|
||||
test_from_env("SOCKS4_SERVER"),
|
||||
test_from_env("SOCKS5_SERVER"),
|
||||
test_from_env("HTTP_SERVER"),
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -132,7 +136,7 @@ mod tests {
|
|||
for potential_test in tests() {
|
||||
match potential_test {
|
||||
Ok(test) => {
|
||||
if filter(&test) {
|
||||
if !filter(&test) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -166,6 +170,16 @@ mod tests {
|
|||
env::var(var).unwrap_or_else(|_| panic!("{}", "{var} environment variable required"));
|
||||
}
|
||||
|
||||
#[serial]
|
||||
#[test_log::test]
|
||||
fn test_socks4() {
|
||||
require_var("SOCKS4_SERVER");
|
||||
run_test(
|
||||
|test| test.proxy.proxy_type == ProxyType::Socks4,
|
||||
request_ip_host_http,
|
||||
)
|
||||
}
|
||||
|
||||
#[serial]
|
||||
#[test_log::test]
|
||||
fn test_socks5() {
|
||||
|
@ -186,6 +200,16 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
#[serial]
|
||||
#[test_log::test]
|
||||
fn test_socks4_dns() {
|
||||
require_var("SOCKS4_SERVER");
|
||||
run_test(
|
||||
|test| test.proxy.proxy_type == ProxyType::Socks4,
|
||||
request_example_https,
|
||||
)
|
||||
}
|
||||
|
||||
#[serial]
|
||||
#[test_log::test]
|
||||
fn test_socks5_dns() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue