Skip to content

Commit 43ceeef

Browse files
authored
fix: fix trajan protocol (#2)
1 parent 10e020f commit 43ceeef

2 files changed

Lines changed: 131 additions & 57 deletions

File tree

src/protocols/singbox/common/tls.rs

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,27 @@ pub struct ExternalAccount {
4949
#[skip_serializing_none]
5050
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
5151
pub struct Ech {
52-
enabled: Option<bool>,
53-
pq_signature_schemes_enabled: Option<bool>,
54-
dynamic_record_sizing_disabled: Option<bool>,
55-
key: Option<Vec<String>>,
56-
key_path: Option<String>,
52+
pub enabled: Option<bool>,
53+
pub config: Option<SingleOrMultipleValue<String>>,
54+
pub config_path: Option<String>,
55+
pub query_server_name: Option<String>,
56+
// deprecated fields kept for backwards compat
57+
pub pq_signature_schemes_enabled: Option<bool>,
58+
pub dynamic_record_sizing_disabled: Option<bool>,
59+
pub key: Option<Vec<String>>,
60+
pub key_path: Option<String>,
5761
}
5862

5963
#[skip_serializing_none]
6064
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
6165
pub struct Reality {
62-
enabled: Option<bool>,
63-
handshake: Option<RealityHandshake>,
64-
public_key: Option<String>,
65-
private_key: Option<String>,
66-
short_id: Option<SingleOrMultipleValue<String>>,
67-
max_time_difference: Option<String>,
66+
pub enabled: Option<bool>,
67+
pub public_key: Option<String>,
68+
pub short_id: Option<SingleOrMultipleValue<String>>,
69+
// server-side fields
70+
pub handshake: Option<RealityHandshake>,
71+
pub private_key: Option<String>,
72+
pub max_time_difference: Option<String>,
6873
}
6974

7075
#[skip_serializing_none]
@@ -123,14 +128,36 @@ pub struct Inbound {
123128
#[derive(Serialize, Deserialize, Debug, Clone)]
124129
pub struct Outbound {
125130
pub enabled: Option<bool>,
126-
pub insecure: Option<bool>,
131+
pub engine: Option<String>,
132+
pub disable_sni: Option<bool>,
127133
pub server_name: Option<String>,
128-
pub certificate: Option<SingleOrMultipleValue<String>>,
129-
pub fingerprint: Option<TlsFingerprint>,
134+
pub insecure: Option<bool>,
130135
pub alpn: Option<Vec<String>>,
131136
pub alpn_mode: Option<AlpnMode>,
132137
pub min_version: Option<String>,
133138
pub max_version: Option<String>,
139+
pub cipher_suites: Option<Vec<String>>,
140+
pub curve_preferences: Option<Vec<String>>,
141+
pub certificate: Option<SingleOrMultipleValue<String>>,
142+
pub certificate_path: Option<String>,
143+
pub certificate_public_key_sha256: Option<Vec<String>>,
144+
pub client_certificate: Option<SingleOrMultipleValue<String>>,
145+
pub client_certificate_path: Option<String>,
146+
pub client_key: Option<SingleOrMultipleValue<String>>,
147+
pub client_key_path: Option<String>,
148+
pub fragment: Option<bool>,
149+
pub fragment_fallback_delay: Option<String>,
150+
pub record_fragment: Option<bool>,
151+
pub spoof: Option<String>,
152+
pub spoof_method: Option<String>,
153+
pub kernel_tx: Option<bool>,
154+
pub kernel_rx: Option<bool>,
155+
pub handshake_timeout: Option<String>,
156+
pub ech: Option<Ech>,
157+
pub utls: Option<Utls>,
158+
pub reality: Option<Reality>,
159+
// deprecated fields kept for backwards compatibility during deserialization
160+
pub fingerprint: Option<TlsFingerprint>,
134161
pub session_ticket: Option<bool>,
135162
pub curves: Option<Vec<String>>,
136163
pub signature_algorithms: Option<String>,
@@ -140,7 +167,13 @@ pub struct Outbound {
140167
pub early_data_size: Option<u32>,
141168
pub session_cache_size: Option<u32>,
142169
pub session_cache_timeout: Option<u64>,
143-
pub client_certificate: Option<ClientCertificateConfig>,
170+
}
171+
172+
#[skip_serializing_none]
173+
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
174+
pub struct Utls {
175+
pub enabled: Option<bool>,
176+
pub fingerprint: Option<String>,
144177
}
145178

146179
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
@@ -163,10 +196,3 @@ pub enum AlpnMode {
163196
Strict,
164197
}
165198

166-
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
167-
pub struct ClientCertificateConfig {
168-
pub certificate_path: Option<String>,
169-
pub key_path: Option<String>,
170-
pub password: Option<String>,
171-
pub ocsp_stapling: Option<u64>,
172-
}

src/protocols/singbox/template_processor.rs

Lines changed: 83 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ impl SingboxProcessor {
7878
serde_json::to_string(&names).unwrap_or_else(|_| "[]".to_string())
7979
}
8080

81-
/// Convert Clash VMess parameters to Sing-box format
81+
/// Convert VMess parameters to Sing-box format
82+
/// Handles both Clash-style flat params and Sing-box-style nested objects
8283
pub fn convert_vmess_params_to_singbox(
8384
config: &mut serde_json::Map<String, serde_json::Value>,
8485
params: &std::collections::HashMap<String, serde_json::Value>,
@@ -102,31 +103,44 @@ impl SingboxProcessor {
102103
}
103104
}
104105

106+
// TLS handling — detect sing-box style nested tls object vs Clash-style boolean
107+
if let Some(tls) = params.get("tls") {
108+
if let Some(tls_obj) = tls.as_object() {
109+
// Sing-box style: tls is already a full object, pass through
110+
let mut tls_config = tls_obj.clone();
111+
tls_config
112+
.entry("enabled".to_string())
113+
.or_insert(serde_json::Value::Bool(true));
114+
config.insert("tls".to_string(), serde_json::Value::Object(tls_config));
115+
} else if tls.as_bool().unwrap_or(false) {
116+
// Clash style: tls is a boolean, build from flat params
117+
let mut tls_config = serde_json::Map::new();
118+
tls_config.insert("enabled".to_string(), serde_json::Value::Bool(true));
119+
120+
if let Some(servername) = params.get("servername").or(params.get("sni")) {
121+
tls_config.insert("server_name".to_string(), servername.clone());
122+
}
105123

106-
// TLS handling
107-
let tls_enabled = params
108-
.get("tls")
109-
.and_then(|v| v.as_bool())
110-
.unwrap_or(false);
111-
112-
if tls_enabled {
113-
let mut tls_config = serde_json::Map::new();
114-
tls_config.insert("enabled".to_string(), serde_json::Value::Bool(true));
124+
if let Some(skip) = params.get("skip-cert-verify") {
125+
tls_config.insert("insecure".to_string(), skip.clone());
126+
}
115127

116-
// servername → server_name
117-
if let Some(servername) = params.get("servername").or(params.get("sni")) {
118-
tls_config.insert("server_name".to_string(), servername.clone());
128+
config.insert("tls".to_string(), serde_json::Value::Object(tls_config));
119129
}
130+
}
120131

121-
// skip-cert-verify → insecure
122-
if let Some(skip) = params.get("skip-cert-verify") {
123-
tls_config.insert("insecure".to_string(), skip.clone());
132+
// Transport handling — detect sing-box style nested transport object first
133+
if let Some(transport) = params.get("transport") {
134+
if let Some(transport_obj) = transport.as_object() {
135+
config.insert(
136+
"transport".to_string(),
137+
serde_json::Value::Object(transport_obj.clone()),
138+
);
139+
return;
124140
}
125-
126-
config.insert("tls".to_string(), serde_json::Value::Object(tls_config));
127141
}
128142

129-
// Transport handling (ws, grpc, h2, http)
143+
// Clash-style transport from "network" + opts
130144
if let Some(network) = params.get("network").and_then(|v| v.as_str()) {
131145
let mut transport = serde_json::Map::new();
132146
transport.insert(
@@ -194,30 +208,42 @@ impl SingboxProcessor {
194208
}
195209
}
196210

197-
/// Convert Clash Trojan parameters to Sing-box format
211+
/// Convert Trojan parameters to Sing-box format
212+
/// Handles both Clash-style flat params (sni, skip-cert-verify) and
213+
/// Sing-box-style nested objects (tls: {enabled, server_name, insecure})
198214
pub fn convert_trojan_params_to_singbox(
199215
config: &mut serde_json::Map<String, serde_json::Value>,
200216
params: &std::collections::HashMap<String, serde_json::Value>,
201217
) {
202-
// TLS is typically enabled by default for Trojan
203-
let mut tls_config = serde_json::Map::new();
204-
tls_config.insert("enabled".to_string(), serde_json::Value::Bool(true));
205-
206-
if let Some(sni) = params.get("sni").or(params.get("servername")) {
207-
tls_config.insert("server_name".to_string(), sni.clone());
208-
}
209-
210-
if let Some(skip) = params.get("skip-cert-verify") {
211-
tls_config.insert("insecure".to_string(), skip.clone());
218+
// Check if params already contain a sing-box style tls object
219+
if let Some(tls) = params.get("tls") {
220+
if let Some(tls_obj) = tls.as_object() {
221+
let mut tls_config = tls_obj.clone();
222+
tls_config
223+
.entry("enabled".to_string())
224+
.or_insert(serde_json::Value::Bool(true));
225+
config.insert("tls".to_string(), serde_json::Value::Object(tls_config));
226+
} else {
227+
// tls is a boolean or other non-object — build from flat params
228+
Self::build_trojan_tls_from_flat_params(config, params);
229+
}
230+
} else {
231+
// No tls key — build from Clash-style flat params
232+
Self::build_trojan_tls_from_flat_params(config, params);
212233
}
213234

214-
if let Some(alpn) = params.get("alpn") {
215-
tls_config.insert("alpn".to_string(), alpn.clone());
235+
// Check if params already contain a sing-box style transport object
236+
if let Some(transport) = params.get("transport") {
237+
if let Some(transport_obj) = transport.as_object() {
238+
config.insert(
239+
"transport".to_string(),
240+
serde_json::Value::Object(transport_obj.clone()),
241+
);
242+
return;
243+
}
216244
}
217245

218-
config.insert("tls".to_string(), serde_json::Value::Object(tls_config));
219-
220-
// Transport handling
246+
// Clash-style transport from "network" + opts
221247
if let Some(network) = params.get("network").and_then(|v| v.as_str()) {
222248
let mut transport = serde_json::Map::new();
223249
transport.insert(
@@ -254,6 +280,28 @@ impl SingboxProcessor {
254280
}
255281
}
256282
}
283+
284+
fn build_trojan_tls_from_flat_params(
285+
config: &mut serde_json::Map<String, serde_json::Value>,
286+
params: &std::collections::HashMap<String, serde_json::Value>,
287+
) {
288+
let mut tls_config = serde_json::Map::new();
289+
tls_config.insert("enabled".to_string(), serde_json::Value::Bool(true));
290+
291+
if let Some(sni) = params.get("sni").or(params.get("servername")) {
292+
tls_config.insert("server_name".to_string(), sni.clone());
293+
}
294+
295+
if let Some(skip) = params.get("skip-cert-verify") {
296+
tls_config.insert("insecure".to_string(), skip.clone());
297+
}
298+
299+
if let Some(alpn) = params.get("alpn") {
300+
tls_config.insert("alpn".to_string(), alpn.clone());
301+
}
302+
303+
config.insert("tls".to_string(), serde_json::Value::Object(tls_config));
304+
}
257305
}
258306

259307
impl ProtocolProcessor for SingboxProcessor {

0 commit comments

Comments
 (0)