@@ -60,6 +60,14 @@ class ConfigParams:
6060 hy2_obfs : Optional [str ] = ""
6161 hy2_obfs_password : Optional [str ] = ""
6262
63+ # * Mvless Extra Fields
64+ mux_enabled : bool = False
65+ mux_concurrency : int = 8
66+ fragment_enabled : bool = False
67+ fragment_packets : Optional [str ] = ""
68+ fragment_length : Optional [str ] = ""
69+ fragment_interval : Optional [str ] = ""
70+
6371
6472def _parse_query_params (query : str ) -> Dict [str , str ]:
6573 """* A utility to parse URL query parameters into a dictionary."""
@@ -83,7 +91,7 @@ def parse_uri(config_uri: str) -> Optional[ConfigParams]:
8391 return None
8492
8593 parser_map = {
86- "vless" : _parse_vless , "vmess" : _parse_vmess , "trojan" : _parse_trojan ,
94+ "vless" : _parse_vless , "mvless" : _parse_vless , " vmess" : _parse_vmess , "trojan" : _parse_trojan ,
8795 "ss" : _parse_shadowsocks , "socks" : _parse_socks , "wireguard" : _parse_wireguard ,
8896 "hysteria" : _parse_hysteria , "hysteria2" : _parse_hysteria , "hy2" : _parse_hysteria ,
8997 }
@@ -102,8 +110,10 @@ def parse_uri(config_uri: str) -> Optional[ConfigParams]:
102110 elif protocol != 'vmess' :
103111 print (f"! Could not extract host/port from URI. Skipping: { uri [:40 ]} ..." )
104112 return None
105-
106- return parser (uri , common )
113+ params = parser (uri , common )
114+ if protocol == "mvless" and params :
115+ _parse_mvless_extensions (params , uri )
116+ return params
107117
108118 except Exception as e :
109119 print (f"! CRITICAL ERROR while parsing URI '{ config_uri [:30 ]} ...': { e } " )
@@ -122,6 +132,25 @@ def _parse_vless(uri: str, common: dict) -> ConfigParams:
122132 fp = params .get ("fp" , "" ), alpn = params .get ("alpn" , "" ), flow = params .get ("flow" , "" ),
123133 encryption = params .get ("encryption" , "none" ),
124134 )
135+ def _parse_mvless_extensions (params : ConfigParams , uri : str ):
136+ """Parses Mux and Fragment parameters specific to the Mvless protocol and modifies the ConfigParams object."""
137+ try :
138+ query_params = urllib .parse .parse_qs (urllib .parse .urlparse (uri ).query )
139+ if 'mux' in query_params and query_params ['mux' ][0 ].upper () == 'ON' :
140+ params .mux_enabled = True
141+ if 'muxConcurrency' in query_params :
142+ try :
143+ params .mux_concurrency = int (query_params ['muxConcurrency' ][0 ])
144+ except (ValueError , IndexError ):
145+ pass
146+
147+ if 'packets' in query_params and 'length' in query_params and 'interval' in query_params :
148+ params .fragment_enabled = True
149+ params .fragment_packets = query_params ['packets' ][0 ]
150+ params .fragment_length = query_params ['length' ][0 ]
151+ params .fragment_interval = query_params ['interval' ][0 ]
152+ except Exception as e :
153+ print (f"! Error parsing mvless extensions: { e } " )
125154
126155def _parse_vmess (uri : str , common : dict ) -> ConfigParams :
127156 encoded_part = uri .replace ("vmess://" , "" )
@@ -230,18 +259,16 @@ def build_outbound_from_params(self, params: ConfigParams, fragment_config: Opti
230259 * The main engine. Converts ConfigParams into a complete Xray outbound dictionary.
231260 * Now correctly maps short protocol names to Xray's official protocol names.
232261 """
233- # ! =========================================================
234- # ! === THE FINAL FIX: MAP SHORT NAMES TO XRAY'S REAL NAMES ===
235- # ! =========================================================
236262 protocol_map = {
237263 "vless" : "vless" ,
264+ "mvless" : "mvless" ,
238265 "vmess" : "vmess" ,
239266 "trojan" : "trojan" ,
240- "ss" : "shadowsocks" , # This was the main bug
267+ "ss" : "shadowsocks" ,
241268 "socks" : "socks" ,
242269 "wireguard" : "wireguard" ,
243270 }
244-
271+
245272 xray_protocol_name = protocol_map .get (params .protocol )
246273 if not xray_protocol_name :
247274 # This protocol is not meant for Xray (like Hysteria)
@@ -251,7 +278,11 @@ def build_outbound_from_params(self, params: ConfigParams, fragment_config: Opti
251278 stream_settings = self ._build_stream_settings (params , fragment = use_fragment , ** kwargs )
252279
253280 protocol_settings = self ._build_protocol_settings (params )
254-
281+ if params .protocol == "mvless" and params .mux_enabled :
282+ try :
283+ outbound ["mux" ] = {"enabled" : True if outbound ["mux" ].upper () == "ON" else False , "concurrency" : params .mux_concurrency }
284+ except Exception :
285+ print ("! No mux found in mvless" )
255286 outbound = {
256287 "tag" : params .tag ,
257288 "protocol" : xray_protocol_name , # ! Use the correct, full protocol name
@@ -277,17 +308,28 @@ def _build_stream_settings(self, params: ConfigParams, **kwargs) -> Dict[str, An
277308 stream_settings ["tlsSettings" ] = security_settings
278309
279310 header_config = {"type" : params .header_type if params .header_type else "none" }
280- if params .header_type == "http" :
281- header_config ["request" ] = {"method" : "GET" , "path" : ["/" ], "headers" : {"Host" : [params .host ], "User-Agent" : ["Mozilla/5.0" ], "Connection" : ["keep-alive" ]}}
311+ host_for_header = params .host if params .host else params .sni
282312
283313 network_map = {
284314 "tcp" : {"tcpSettings" : {"header" : header_config }}, "kcp" : {"kcpSettings" : {"header" : header_config , "seed" : params .path }},
285- "ws" : {"wsSettings" : {"path" : params .path , "headers" : {"Host" : params .host }}}, "h2" : {"httpSettings" : {"host" : [params .host ], "path" : params .path }},
286- "quic" : {"quicSettings" : {"security" : params .host , "key" : params .path , "header" : header_config }}, "grpc" : {"grpcSettings" : {"serviceName" : params .path , "multiMode" : (params .mode == "multi" )}},
315+ "ws" : {"wsSettings" : {"path" : params .path , "headers" : {"Host" : host_for_header }}},
316+ "h2" : {"httpSettings" : {"host" : [host_for_header ], "path" : params .path }},
317+ "quic" : {"quicSettings" : {"security" : params .host , "key" : params .path , "header" : header_config }},
318+ "grpc" : {"grpcSettings" : {"serviceName" : params .path , "multiMode" : (params .mode == "multi" )}},
287319 }
288320 stream_settings .update (network_map .get (params .network , {}))
321+ if params .protocol == "mvless" and params .fragment_enabled :
322+ stream_settings ["fragment" ] = {
323+ "packets" : params .fragment_packets ,
324+ "length" : params .fragment_length ,
325+ "interval" : params .fragment_interval
326+ }
327+
328+
329+
330+ if kwargs .get ("fragment_config" ) and not params .fragment_enabled :
331+ stream_settings ["sockopt" ] = {"dialerProxy" : "fragment" }
289332
290- if kwargs .get ("fragment" ): stream_settings ["sockopt" ] = {"dialerProxy" : "fragment" , "mark" : 255 }
291333 return stream_settings
292334 def add_fragment_outbound (self , fragment_config : Dict [str , Any ]):
293335 """
@@ -314,7 +356,11 @@ def add_fragment_outbound(self, fragment_config: Dict[str, Any]):
314356 def _build_protocol_settings (self , params : ConfigParams ) -> Dict [str , Any ]:
315357 level = 0
316358 protocol = params .protocol
317- if protocol == "vless" : return {"vnext" : [{"address" : params .address , "port" : params .port , "users" : [{"id" : params .id , "flow" : params .flow , "encryption" : "none" , "level" : level }]}]}
359+ if protocol in ["vless" , "mvless" ]:
360+ settings = {"vnext" : [{"address" : params .address , "port" : params .port , "users" : [{"id" : params .id , "flow" : params .flow , "encryption" : "none" , "level" : level }]}]}
361+ if protocol == "mvless" and params .fragment_enabled :
362+ pass
363+ return settings
318364 elif protocol == "vmess" : return {"vnext" : [{"address" : params .address , "port" : params .port , "users" : [{"id" : params .id , "alterId" : params .alter_id , "security" : params .scy , "level" : level }]}]}
319365 elif protocol == "trojan" : return {"servers" : [{"address" : params .address , "port" : params .port , "password" : params .password , "level" : level }]}
320366 elif protocol == "ss" :
0 commit comments