Skip to content

Commit

Permalink
feat: support Socks5 TCP + UDP outbound (#491)
Browse files Browse the repository at this point in the history
  • Loading branch information
ibigbug authored Jul 13, 2024
1 parent d62784f commit c0eb0b6
Show file tree
Hide file tree
Showing 23 changed files with 774 additions and 53 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ config.yaml
cache.db
Country.mmdb
ruleset/
geosite.dat

rust-project.json

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ A custom protocol, rule based network proxy software.
- 🌈 Flexible traffic routing rules based off source/destination IP/Domain/GeoIP etc.
- 📦 Local anti spoofing DNS with support of UDP/TCP/DoH/DoT remote.
- 🛡 Run as an HTTP/Socks5 proxy, or utun device as a home network gateway.
- ⚙️ Shadowsocks/Trojan/Vmess/Wireguard(userspace)/Tor/Tuic outbound support with different underlying trasports(gRPC/TLS/H2/WebSocket/etc.).
- ⚙️ Shadowsocks/Trojan/Vmess/Wireguard(userspace)/Tor/Tuic/Socks5(TCP/UDP) outbound support with different underlying trasports(gRPC/TLS/H2/WebSocket/etc.).
- 🌍 Dynamic remote rule/proxy loader.
- 🎵 Tracing with Jaeger

Expand Down
39 changes: 39 additions & 0 deletions clash/tests/data/config/socks5.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
port: 8888
socks-port: 8889
mixed-port: 8899

mode: rule
log-level: debug
external-controller: 127.0.0.1:6170


proxies:
- name: "socks5-noauth"
type: socks5
server: 10.0.0.13
port: 10800
udp: true

- name: "socks5-auth"
type: socks5
server: 10.0.0.13
port: 10801
username: user
password: password
udp: true

- name: "socks5-tls"
type: socks5
server: 10.0.0.13
port: 10802
username: user
password: password
tls: true
udp: true
skip-cert-verify: true
rules:
# - MATCH, socks5-noauth
# - MATCH, socks5-auth
- MATCH, socks5-tls
...
7 changes: 4 additions & 3 deletions clash_lib/src/app/outbound/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ impl OutboundManager {
handlers.insert(s.name.clone(), s.try_into()?);
}

OutboundProxyProtocol::Socks5(s) => {
handlers.insert(s.name.clone(), s.try_into()?);
}

OutboundProxyProtocol::Vmess(v) => {
handlers.insert(v.name.clone(), v.try_into()?);
}
Expand All @@ -222,9 +226,6 @@ impl OutboundManager {
OutboundProxyProtocol::Tuic(tuic) => {
handlers.insert(tuic.name.clone(), tuic.try_into()?);
}
p => {
unimplemented!("proto {} not supported yet", p);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,7 @@ impl ProxySetProvider {
Ok(reject::Handler::new())
}
OutboundProxyProtocol::Ss(s) => s.try_into(),
OutboundProxyProtocol::Socks5(_) => {
todo!("socks5 not supported yet")
}
OutboundProxyProtocol::Socks5(s) => s.try_into(),
OutboundProxyProtocol::Trojan(tr) => tr.try_into(),
OutboundProxyProtocol::Vmess(vm) => vm.try_into(),
OutboundProxyProtocol::Wireguard(wg) => wg.try_into(),
Expand Down
8 changes: 8 additions & 0 deletions clash_lib/src/common/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,18 @@ pub fn md5(bytes: &[u8]) -> Vec<u8> {
hasher.finalize().to_vec()
}

/// Default value true for bool on serde
/// use this if you don't want do deal with Option<bool>
pub fn default_bool_true() -> bool {
true
}

/// Default value false for bool on serde
/// use this if you don't want do deal with Option<bool>
pub fn default_bool_false() -> bool {
false
}

#[async_recursion]
pub async fn download<P>(
url: &str,
Expand Down
16 changes: 13 additions & 3 deletions clash_lib/src/config/internal/proxy.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use crate::{common::utils::default_bool_true, config::utils, Error};
use crate::{
common::utils::{default_bool_false, default_bool_true},
config::utils,
Error,
};
use serde::{de::value::MapDeserializer, Deserialize};
use serde_yaml::Value;
use std::{
Expand Down Expand Up @@ -116,6 +120,7 @@ impl Display for OutboundProxyProtocol {
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Default)]
#[serde(rename_all = "kebab-case")]
pub struct OutboundShadowsocks {
pub name: String,
pub server: String,
Expand All @@ -125,23 +130,28 @@ pub struct OutboundShadowsocks {
#[serde(default = "default_bool_true")]
pub udp: bool,
pub plugin: Option<String>,
#[serde(alias = "plugin-opts")]
pub plugin_opts: Option<HashMap<String, serde_yaml::Value>>,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Default)]
#[serde(rename_all = "kebab-case")]
pub struct OutboundSocks5 {
pub name: String,
pub server: String,
pub port: u16,
pub username: Option<String>,
pub password: Option<String>,
#[serde(default = "default_bool_false")]
pub tls: bool,
pub skip_cert_verity: bool,
pub sni: Option<String>,
#[serde(default = "default_bool_false")]
pub skip_cert_verify: bool,
#[serde(default = "default_bool_true")]
pub udp: bool,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Default)]
#[serde(rename_all = "kebab-case")]
pub struct WsOpt {
pub path: Option<String>,
pub headers: Option<HashMap<String, String>>,
Expand Down
1 change: 1 addition & 0 deletions clash_lib/src/proxy/converters/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod shadowsocks;
pub mod socks5;
pub mod tor;
pub mod trojan;
pub mod tuic;
Expand Down
35 changes: 35 additions & 0 deletions clash_lib/src/proxy/converters/socks5.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use crate::{
config::internal::proxy::OutboundSocks5,
proxy::{
socks::{Handler, HandlerOptions},
AnyOutboundHandler,
},
};

impl TryFrom<OutboundSocks5> for AnyOutboundHandler {
type Error = crate::Error;

fn try_from(value: OutboundSocks5) -> Result<Self, Self::Error> {
(&value).try_into()
}
}

impl TryFrom<&OutboundSocks5> for AnyOutboundHandler {
type Error = crate::Error;

fn try_from(s: &OutboundSocks5) -> Result<Self, Self::Error> {
let h = Handler::new(HandlerOptions {
name: s.name.to_owned(),
common_opts: Default::default(),
server: s.server.to_owned(),
port: s.port,
user: s.username.clone(),
password: s.password.clone(),
udp: s.udp,
tls: s.tls,
sni: s.sni.clone().unwrap_or(s.server.to_owned()),
skip_cert_verify: s.skip_cert_verify,
});
Ok(h)
}
}
4 changes: 2 additions & 2 deletions clash_lib/src/proxy/converters/trojan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
config::internal::proxy::OutboundTrojan,
proxy::{
options::{GrpcOption, WsOption},
trojan::{Handler, Opts, Transport},
trojan::{Handler, HandlerOptions, Transport},
AnyOutboundHandler, CommonOption,
},
Error,
Expand All @@ -27,7 +27,7 @@ impl TryFrom<&OutboundTrojan> for AnyOutboundHandler {
warn!("skipping TLS cert verification for {}", s.server);
}

let h = Handler::new(Opts {
let h = Handler::new(HandlerOptions {
name: s.name.to_owned(),
common_opts: CommonOption::default(),
server: s.server.to_owned(),
Expand Down
4 changes: 2 additions & 2 deletions clash_lib/src/proxy/converters/wireguard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use ipnet::IpNet;
use crate::{
config::internal::proxy::OutboundWireguard,
proxy::{
wg::{Handler, HandlerOpts},
wg::{Handler, HandlerOptions},
AnyOutboundHandler,
},
Error,
Expand All @@ -21,7 +21,7 @@ impl TryFrom<&OutboundWireguard> for AnyOutboundHandler {
type Error = crate::Error;

fn try_from(s: &OutboundWireguard) -> Result<Self, Self::Error> {
let h = Handler::new(HandlerOpts {
let h = Handler::new(HandlerOptions {
name: s.name.to_owned(),
server: s.server.to_owned(),
port: s.port,
Expand Down
4 changes: 4 additions & 0 deletions clash_lib/src/proxy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ pub enum OutboundType {
WireGuard,
Tor,
Tuic,
Socks5,

#[serde(rename = "URLTest")]
UrlTest,
Expand All @@ -143,11 +144,14 @@ impl Display for OutboundType {
OutboundType::WireGuard => write!(f, "WireGuard"),
OutboundType::Tor => write!(f, "Tor"),
OutboundType::Tuic => write!(f, "Tuic"),
OutboundType::Socks5 => write!(f, "Socks5"),

OutboundType::UrlTest => write!(f, "URLTest"),
OutboundType::Selector => write!(f, "Selector"),
OutboundType::Relay => write!(f, "Relay"),
OutboundType::LoadBalance => write!(f, "LoadBalance"),
OutboundType::Fallback => write!(f, "Fallback"),

OutboundType::Direct => write!(f, "Direct"),
OutboundType::Reject => write!(f, "Reject"),
}
Expand Down
26 changes: 0 additions & 26 deletions clash_lib/src/proxy/socks/inbound/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,6 @@ use tracing::warn;

pub use datagram::Socks5UDPCodec;

pub const SOCKS5_VERSION: u8 = 0x05;

pub(crate) mod auth_methods {
pub const NO_AUTH: u8 = 0x00;
pub const USER_PASS: u8 = 0x02;
pub const NO_METHODS: u8 = 0xff;
}

pub(crate) mod response_code {
pub const SUCCEEDED: u8 = 0x00;
pub const FAILURE: u8 = 0x01;
// pub const RULE_FAILURE: u8 = 0x02;
// pub const NETWORK_UNREACHABLE: u8 = 0x03;
// pub const HOST_UNREACHABLE: u8 = 0x04;
// pub const CONNECTION_REFUSED: u8 = 0x05;
// pub const TTL_EXPIRED: u8 = 0x06;
pub const COMMAND_NOT_SUPPORTED: u8 = 0x07;
// pub const ADDR_TYPE_NOT_SUPPORTED: u8 = 0x08;
}

pub(crate) mod socks_command {
pub const CONNECT: u8 = 0x01;
// pub const BIND: u8 = 0x02;
pub const UDP_ASSOCIATE: u8 = 0x3;
}

pub struct Listener {
addr: SocketAddr,
dispatcher: Arc<Dispatcher>,
Expand Down
7 changes: 4 additions & 3 deletions clash_lib/src/proxy/socks/inbound/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use crate::{
common::{auth::ThreadSafeAuthenticator, errors::new_io_error},
proxy::{
datagram::InboundUdp,
socks::inbound::{
auth_methods, datagram::Socks5UDPCodec, response_code, socks_command,
SOCKS5_VERSION,
socks::{
socks5::{auth_methods, response_code, socks_command},
Socks5UDPCodec, SOCKS5_VERSION,
},
utils::new_udp_socket,
},
Expand All @@ -31,6 +31,7 @@ pub async fn handle_tcp<'a>(
// handshake
let mut buf = BytesMut::new();
{
// TODO: move this to a function
buf.resize(2, 0);
s.read_exact(&mut buf[..]).await?;

Expand Down
6 changes: 5 additions & 1 deletion clash_lib/src/proxy/socks/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
mod inbound;
mod outbound;
mod socks5;

pub use inbound::{handle_tcp, Listener, Socks5UDPCodec, SOCKS5_VERSION};
pub use inbound::{handle_tcp, Listener, Socks5UDPCodec};
pub use outbound::{Handler, HandlerOptions};
pub use socks5::SOCKS5_VERSION;
Loading

0 comments on commit c0eb0b6

Please sign in to comment.