From 65721cd5e3820a7dfa6035dec8d197f286fc8f15 Mon Sep 17 00:00:00 2001 From: Keivan-sf Date: Sun, 27 Jul 2025 21:50:52 +0330 Subject: [PATCH] Parse socks --- src/config_models/mod.rs | 21 ++++++++ src/parser/mod.rs | 6 +++ src/parser/shadow_socks/data.rs | 1 + src/parser/socks/data.rs | 87 +++++++++++++++++++++++++++++++++ src/parser/socks/mod.rs | 20 ++++++++ src/parser/socks/models.rs | 6 +++ src/parser/trojan/data.rs | 1 + src/parser/uri_identifier.rs | 6 +-- src/parser/vless/data.rs | 1 + src/parser/vmess/data.rs | 2 + 10 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 src/parser/socks/data.rs create mode 100644 src/parser/socks/mod.rs create mode 100644 src/parser/socks/models.rs diff --git a/src/config_models/mod.rs b/src/config_models/mod.rs index 5c94b68..a836175 100644 --- a/src/config_models/mod.rs +++ b/src/config_models/mod.rs @@ -9,6 +9,12 @@ pub struct VnextUser { pub security: Option, } +#[derive(Serialize, Deserialize)] +pub struct SocksUser { + pub user: Option, + pub pass: Option, +} + #[derive(Serialize, Deserialize)] pub struct VnextServerObject { pub address: Option, @@ -33,6 +39,14 @@ pub struct ShadowSocksServerObject { pub method: Option, } +#[derive(Serialize, Deserialize)] +pub struct SocksServerObject { + pub address: Option, + pub port: Option, + pub level: Option, + pub users: Option>, +} + #[derive(Serialize, Deserialize)] pub struct VlessOutboundSettings { pub vnext: Vec, @@ -53,6 +67,11 @@ pub struct ShadowSocksOutboundSettings { pub servers: Vec, } +#[derive(Serialize, Deserialize)] +pub struct SocksOutboundSettings { + pub servers: Vec, +} + #[derive(Serialize, Deserialize)] #[serde(untagged)] pub enum OutboundSettings { @@ -60,6 +79,7 @@ pub enum OutboundSettings { Vmess(VmessOutboundSettings), Trojan(TrojanOutboundSettings), ShadowSocks(ShadowSocksOutboundSettings), + Socks(SocksOutboundSettings), } #[derive(Serialize, Deserialize)] @@ -243,4 +263,5 @@ pub struct RawData { pub address: Option, pub port: Option, pub server_method: Option, + pub username: Option, } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c5ce531..4ac9d9c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6,6 +6,7 @@ use crate::config_models::{ use crate::utils::{inbound_generator, parse_raw_json}; mod shadow_socks; +mod socks; mod trojan; mod uri_identifier; mod vless; @@ -174,6 +175,11 @@ fn get_uri_data(uri: &str) -> (String, RawData, OutboundSettings) { let s = shadow_socks::create_outbound_settings(&d); (String::from("shadowsocks"), d, s) } + Some(uri_identifier::Protocols::Socks) => { + let d = socks::data::get_data(uri); + let s = socks::create_outbound_settings(&d); + (String::from("socks"), d, s) + } Some(_) => { panic!("The protocol was recognized but is not supported yet"); } diff --git a/src/parser/shadow_socks/data.rs b/src/parser/shadow_socks/data.rs index 7f1c474..011d07e 100644 --- a/src/parser/shadow_socks/data.rs +++ b/src/parser/shadow_socks/data.rs @@ -41,6 +41,7 @@ pub fn get_data(uri: &str) -> RawData { quic_security: None, allowInsecure: None, vnext_security: None, + username: None, }; } diff --git a/src/parser/socks/data.rs b/src/parser/socks/data.rs new file mode 100644 index 0000000..d1a1196 --- /dev/null +++ b/src/parser/socks/data.rs @@ -0,0 +1,87 @@ +use http::Uri; + +use crate::{ + config_models::RawData, + parser::socks::models, + utils::{url_decode, url_decode_str}, +}; +use base64::{engine::general_purpose, Engine}; + +pub fn get_data(uri: &str) -> RawData { + let data = uri.split_once("://").unwrap().1; + let (raw_data, name) = data.split_once("#").unwrap_or((data, "")); + let (raw_uri, _) = raw_data.split_once("?").unwrap_or((raw_data, "")); + let parsed_address = parse_socks_address(raw_uri); + return RawData { + remarks: url_decode(Some(String::from(name))).unwrap_or(String::from("")), + username: url_decode(parsed_address.username), + address: Some(parsed_address.address), + port: Some(parsed_address.port), + uuid: url_decode(parsed_address.password), + r#type: Some(String::from("tcp")), + header_type: None, + server_method: None, + security: None, + fp: None, + sni: None, + pbk: None, + sid: None, + key: None, + spx: None, + flow: None, + path: None, + host: None, + seed: None, + mode: None, + slpn: None, + alpn: None, + extra: None, + authority: None, + encryption: None, + service_name: None, + quic_security: None, + allowInsecure: None, + vnext_security: None, + }; +} + +fn parse_socks_address(raw_data: &str) -> models::SocksAddress { + let (maybe_userinfo, raw_address): (Option, &str) = match raw_data.split_once("@") { + Some(data) => (Some(String::from(data.0)), data.1), + None => (None, raw_data), + }; + let address_wo_slash = raw_address.strip_suffix("/").unwrap_or(raw_address); + + let parsed = address_wo_slash.parse::().unwrap(); + + return match maybe_userinfo { + Some(userinfo) => { + let url_decoded = url_decode_str(&userinfo).unwrap_or(userinfo); + let a = general_purpose::STANDARD + .decode(url_decoded.clone()) + .map(|a| { + String::from( + std::str::from_utf8(&a).expect("Base64 did not yield a valid utf-8 string"), + ) + }) + .unwrap_or(String::from(url_decoded.clone())); + + let (username, password) = a + .split_once(":") + .expect("No `:` found in the decoded base64"); + + models::SocksAddress { + username: Some(String::from(username)), + password: Some(String::from(password)), + address: parsed.host().unwrap().to_string(), + port: parsed.port().unwrap().as_u16(), + } + } + None => models::SocksAddress { + username: None, + password: None, + address: parsed.host().unwrap().to_string(), + port: parsed.port().unwrap().as_u16(), + }, + }; +} diff --git a/src/parser/socks/mod.rs b/src/parser/socks/mod.rs new file mode 100644 index 0000000..eecd912 --- /dev/null +++ b/src/parser/socks/mod.rs @@ -0,0 +1,20 @@ +pub mod data; +mod models; +use crate::config_models::*; + +pub fn create_outbound_settings(data: &RawData) -> OutboundSettings { + return OutboundSettings::Socks(SocksOutboundSettings { + servers: vec![SocksServerObject { + users: match (&data.username, &data.uuid) { + (Some(username), Some(uuid)) => Some(vec![SocksUser { + user: Some(username.clone()), + pass: Some(uuid.clone()), + }]), + _ => None, + }, + address: data.address.clone(), + port: data.port, + level: Some(0), + }], + }); +} diff --git a/src/parser/socks/models.rs b/src/parser/socks/models.rs new file mode 100644 index 0000000..5f0d6e4 --- /dev/null +++ b/src/parser/socks/models.rs @@ -0,0 +1,6 @@ +pub struct SocksAddress { + pub password: Option, + pub username: Option, + pub address: String, + pub port: u16, +} diff --git a/src/parser/trojan/data.rs b/src/parser/trojan/data.rs index e6729e8..5948837 100644 --- a/src/parser/trojan/data.rs +++ b/src/parser/trojan/data.rs @@ -41,6 +41,7 @@ pub fn get_data(uri: &str) -> RawData { extra: url_decode(get_parameter_value(&query, "extra")), allowInsecure: get_parameter_value(&query, "allowInsecure"), server_method: None, + username: None, }; } diff --git a/src/parser/uri_identifier.rs b/src/parser/uri_identifier.rs index 5dad642..d57ae89 100644 --- a/src/parser/uri_identifier.rs +++ b/src/parser/uri_identifier.rs @@ -10,10 +10,6 @@ pub enum Protocols { } pub fn get_uri_protocol(uri: &str) -> Option { - let uri_regex = Regex::new(r"^[a-z]+:\/\/.+$").unwrap(); - if !uri_regex.is_match(uri) { - return None; - } if uri.starts_with("vmess://") { return Some(Protocols::Vmess); } @@ -23,7 +19,7 @@ pub fn get_uri_protocol(uri: &str) -> Option { if uri.starts_with("ss://") { return Some(Protocols::Shadowsocks); } - if uri.starts_with("socks://") { + if uri.starts_with("socks5://") || uri.starts_with("socks4://") || uri.starts_with("socks://") { return Some(Protocols::Socks); } if uri.starts_with("http://") { diff --git a/src/parser/vless/data.rs b/src/parser/vless/data.rs index 944f47a..69b4cf8 100644 --- a/src/parser/vless/data.rs +++ b/src/parser/vless/data.rs @@ -41,6 +41,7 @@ pub fn get_data(uri: &str) -> RawData { extra: url_decode(get_parameter_value(&query, "extra")), allowInsecure: get_parameter_value(&query, "allowInsecure"), server_method: None, + username:None, }; } diff --git a/src/parser/vmess/data.rs b/src/parser/vmess/data.rs index abead67..5d97050 100644 --- a/src/parser/vmess/data.rs +++ b/src/parser/vmess/data.rs @@ -58,6 +58,7 @@ fn get_raw_data_from_base64(decoded_base64: &Vec) -> RawData { // this probably does not exist in vmess uri allowInsecure: None, server_method: None, + username: None, }; } @@ -104,6 +105,7 @@ fn get_raw_data_from_uri(uri: &str) -> RawData { extra: url_decode(get_parameter_value(&query, "extra")), allowInsecure: get_parameter_value(&query, "allowInsecure"), server_method: None, + username: None, }; }