@@ -143,10 +143,15 @@ fn build_singbox_tls(p: &Profile, force: bool, s: &AdvancedSettings) -> Option<V
143143 if !tls. ech . is_empty ( ) {
144144 t[ "ech" ] = json ! ( { "enabled" : true , "config" : [ tls. ech] } ) ;
145145 }
146- // QUIC outbounds (hysteria2/tuic) drive their own TLS stack and reject a uTLS
147- // config — sing-box errors `unsupported usage for uTLS` on every dial.
148- let quic = matches ! ( p, Profile :: Hysteria2 ( _) | Profile :: Tuic ( _) ) ;
149- if tls. fingerprint != Fingerprint :: Empty && !quic {
146+ // Some outbounds reject a uTLS config: hysteria2/tuic drive their own QUIC TLS
147+ // stack (sing-box errors `unsupported usage for uTLS` on every dial), and the
148+ // naive outbound uses a chromium-style TLS (`uTLS is not supported on naive
149+ // outbound` at init). Skip uTLS for those.
150+ let no_utls = matches ! (
151+ p,
152+ Profile :: Hysteria2 ( _) | Profile :: Tuic ( _) | Profile :: Naive ( _)
153+ ) ;
154+ if tls. fingerprint != Fingerprint :: Empty && !no_utls {
150155 t[ "utls" ] = json ! ( { "enabled" : true , "fingerprint" : wire( & tls. fingerprint) } ) ;
151156 }
152157 if tls. security == Security :: Reality {
@@ -1188,4 +1193,42 @@ mod tests {
11881193 let socks = & cfg[ "inbounds" ] . as_array ( ) . unwrap ( ) [ 0 ] ;
11891194 assert ! ( socks. get( "users" ) . is_none( ) ) ;
11901195 }
1196+
1197+ #[ test]
1198+ fn naive_outbound_omits_utls ( ) {
1199+ let build = |uri : & str | {
1200+ let p = crate :: share:: parse_share_link ( uri, None ) . unwrap ( ) ;
1201+ build_singbox_config (
1202+ & p,
1203+ & AdvancedSettings :: default ( ) ,
1204+ & [ ] ,
1205+ std:: slice:: from_ref ( & p) ,
1206+ SingboxBuildOpts :: default ( ) ,
1207+ )
1208+ . unwrap ( )
1209+ } ;
1210+ let proxy = |cfg : & Value | {
1211+ cfg[ "outbounds" ]
1212+ . as_array ( )
1213+ . unwrap ( )
1214+ . iter ( )
1215+ . find ( |o| o[ "tag" ] == "proxy" )
1216+ . cloned ( )
1217+ . unwrap ( )
1218+ } ;
1219+
1220+ // sing-box rejects uTLS on the naive outbound (`uTLS is not supported on
1221+ // naive outbound`), so the builder must omit it even though the profile
1222+ // carries a default fingerprint.
1223+ let naive = proxy ( & build ( "naive+https://user:pw@n.ex:443?sni=s.ex&fp=chrome" ) ) ;
1224+ assert_eq ! ( naive[ "type" ] , "naive" ) ;
1225+ assert ! (
1226+ naive[ "tls" ] [ "utls" ] . is_null( ) ,
1227+ "naive tls must not carry utls"
1228+ ) ;
1229+
1230+ // A protocol that does accept uTLS still gets it (guard against an over-broad skip).
1231+ let anytls = proxy ( & build ( "anytls://pw@a.ex:443?sni=s.ex&fp=chrome" ) ) ;
1232+ assert_eq ! ( anytls[ "tls" ] [ "utls" ] [ "enabled" ] , true ) ;
1233+ }
11911234}
0 commit comments