Skip to content

Commit 9aef2f4

Browse files
committed
2 parents 16fe421 + dd8a882 commit 9aef2f4

File tree

2 files changed

+102
-24
lines changed

2 files changed

+102
-24
lines changed

perf.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from v2ray2proxy.base import V2RayProxy, V2RayCore
2+
from asyncio import run
3+
import logging, requests
4+
from time import time
5+
6+
# Set up logging
7+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s", handlers=[logging.StreamHandler()])
8+
9+
# Test V2Ray link
10+
TEST_LINK = "vless://cb5d5224-b21f-4522-b756-237205084ba9@94.103.169.120:51569?type=tcp&security=none#TelegramMask-rkn-sosat"
11+
12+
13+
async def main():
14+
try:
15+
# Measure startup time
16+
start_time = time()
17+
logging.info("Initializing V2Ray...")
18+
proxy = V2RayProxy(TEST_LINK, socks_port=None)
19+
init_time = time()
20+
logging.info(f"V2Ray initialized in {init_time - start_time:.2f} seconds")
21+
22+
# Measure check time
23+
check_start = time()
24+
logging.info("Checking V2Ray configuration...")
25+
with open("./v2ray_config.json", "w") as f:
26+
f.write(open(proxy.config_file_path, "r").read())
27+
check_end = time()
28+
logging.info(f"Configuration check completed in {check_end - check_start:.2f} seconds")
29+
30+
# request_start = time()
31+
# requests.get(
32+
# "https://api.ipify.org?format=json",
33+
# proxies={"http": proxy.http_proxy_url, "https": proxy.http_proxy_url},
34+
# timeout=30
35+
# )
36+
# request_end = time()
37+
# logging.info(f"request duration: {request_end - request_start:.2f} seconds")
38+
39+
# Measure stop time
40+
stop_start = time()
41+
logging.info("Stopping V2Ray...")
42+
proxy.stop()
43+
stop_end = time()
44+
logging.info(f"V2Ray stopped in {stop_end - stop_start:.2f} seconds")
45+
46+
logging.info(f"Test took {stop_end - start_time:.2f} seconds")
47+
48+
except Exception as e:
49+
logging.error(f"Error during performance test: {e}")
50+
raise
51+
52+
53+
if __name__ == "__main__":
54+
run(main())

v2ray2proxy/base.py

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,8 @@ def _parse_vmess_link(self, link: str) -> dict:
306306
try:
307307
# Remove "vmess://" and decode the base64 content
308308
b64_content = link[8:]
309+
# Add padding if necessary
310+
b64_content += "=" * (-len(b64_content) % 4)
309311
decoded_content = base64.b64decode(b64_content).decode("utf-8")
310312
vmess_info = json.loads(decoded_content)
311313

@@ -357,11 +359,14 @@ def _parse_vless_link(self, link: str) -> dict:
357359
parsed_url = urllib.parse.urlparse(link)
358360

359361
# Extract user info (uuid)
360-
user_info = parsed_url.netloc.split("@")[0]
362+
if "@" not in parsed_url.netloc:
363+
raise ValueError("Invalid VLESS link: missing user info")
364+
user_info, host_port = parsed_url.netloc.split("@", 1)
361365

362366
# Extract host and port
363-
host_port = parsed_url.netloc.split("@")[1]
364-
host, port = host_port.split(":")
367+
if ":" not in host_port:
368+
raise ValueError("Invalid VLESS link: missing port")
369+
host, port = host_port.rsplit(":", 1)
365370

366371
# Parse query parameters
367372
params = dict(urllib.parse.parse_qsl(parsed_url.query))
@@ -370,18 +375,18 @@ def _parse_vless_link(self, link: str) -> dict:
370375
outbound = {
371376
"protocol": "vless",
372377
"settings": {
373-
"vnext": [{"address": host, "port": int(port), "users": [{"id": user_info, "encryption": "none", "level": 0}]}]
378+
"vnext": [{"address": host, "port": int(port), "users": [{"id": user_info, "encryption": params.get("encryption", "none"), "level": 0}]}]
374379
},
375380
"streamSettings": {"network": params.get("type", "tcp"), "security": params.get("security", "none")},
376381
}
377382

378383
# Handle TLS settings
379384
if params.get("security") == "tls":
380-
outbound["streamSettings"]["tlsSettings"] = {"serverName": params.get("sni", "")}
385+
outbound["streamSettings"]["tlsSettings"] = {"serverName": params.get("sni", host)}
381386

382387
# Handle WebSocket settings
383388
if params.get("type") == "ws":
384-
outbound["streamSettings"]["wsSettings"] = {"path": params.get("path", "/"), "headers": {"Host": params.get("host", "")}}
389+
outbound["streamSettings"]["wsSettings"] = {"path": params.get("path", "/"), "headers": {"Host": params.get("host", host)}}
385390

386391
return outbound
387392
except Exception as e:
@@ -394,29 +399,34 @@ def _parse_shadowsocks_link(self, link: str) -> dict:
394399
raise ValueError("Not a valid Shadowsocks link")
395400

396401
try:
397-
# Two possible formats:
398-
# 1. ss://base64(method:password@host:port)#remark
399-
# 2. ss://base64(method:password)@host:port#remark
400-
401402
parsed_url = urllib.parse.urlparse(link)
403+
remark = urllib.parse.unquote(parsed_url.fragment) if parsed_url.fragment else ""
402404

403405
if "@" in parsed_url.netloc:
404-
# Format 2
406+
# Format: ss://base64(method:password)@host:port#remark
405407
user_info_b64, host_port = parsed_url.netloc.split("@", 1)
408+
user_info_b64 += "=" * (-len(user_info_b64) % 4)
406409
user_info = base64.b64decode(user_info_b64).decode("utf-8")
407410
method, password = user_info.split(":", 1)
408-
host, port = host_port.split(":", 1)
411+
host, port = host_port.rsplit(":", 1)
409412
else:
410-
# Format 1
411-
decoded = base64.b64decode(parsed_url.netloc).decode("utf-8")
413+
# Format: ss://base64(method:password@host:port)#remark
414+
decoded_part = parsed_url.netloc
415+
decoded_part += "=" * (-len(decoded_part) % 4)
416+
decoded = base64.b64decode(decoded_part).decode("utf-8")
417+
418+
if "@" not in decoded:
419+
raise ValueError("Invalid Shadowsocks link format")
420+
412421
method_pass, host_port = decoded.split("@", 1)
413422
method, password = method_pass.split(":", 1)
414-
host, port = host_port.split(":", 1)
423+
host, port = host_port.rsplit(":", 1)
415424

416425
# Create outbound configuration
417426
outbound = {
418427
"protocol": "shadowsocks",
419428
"settings": {"servers": [{"address": host, "port": int(port), "method": method, "password": password}]},
429+
"tag": remark
420430
}
421431

422432
return outbound
@@ -433,12 +443,17 @@ def _parse_trojan_link(self, link: str) -> dict:
433443
# Format: trojan://password@host:port?param=value&param2=value2#remark
434444
parsed_url = urllib.parse.urlparse(link)
435445

446+
if "@" not in parsed_url.netloc:
447+
raise ValueError("Invalid Trojan link: missing password or host")
448+
436449
# Extract password
437-
password = parsed_url.netloc.split("@")[0]
450+
password, host_port = parsed_url.netloc.split("@", 1)
451+
password = urllib.parse.unquote(password)
438452

439453
# Extract host and port
440-
host_port = parsed_url.netloc.split("@")[1]
441-
host, port = host_port.split(":")
454+
if ":" not in host_port:
455+
raise ValueError("Invalid Trojan link: missing port")
456+
host, port = host_port.rsplit(":", 1)
442457

443458
# Parse query parameters
444459
params = dict(urllib.parse.parse_qsl(parsed_url.query))
@@ -449,19 +464,29 @@ def _parse_trojan_link(self, link: str) -> dict:
449464
"settings": {"servers": [{"address": host, "port": int(port), "password": password}]},
450465
"streamSettings": {
451466
"network": params.get("type", "tcp"),
452-
"security": "tls",
467+
"security": params.get("security", "tls"), # Default to tls for trojan
453468
"tlsSettings": {"serverName": params.get("sni", host)},
454469
},
455470
}
471+
472+
if params.get("type") == "ws":
473+
outbound["streamSettings"]["wsSettings"] = {
474+
"path": params.get("path", "/"),
475+
"headers": {"Host": params.get("host", host)}
476+
}
456477

457478
return outbound
458479
except Exception as e:
459480
logging.error(f"Failed to parse Trojan link: {str(e)}")
460481
raise ValueError(f"Invalid Trojan format: {str(e)}")
461482

483+
def _prepare_link(self):
484+
...
485+
462486
def generate_config(self):
463487
"""Generate V2Ray configuration from link."""
464488
try:
489+
# self.v2ray_link._prepare_link()
465490
# Determine the type of link and parse accordingly
466491
if self.v2ray_link.startswith("vmess://"):
467492
outbound = self._parse_vmess_link(self.v2ray_link)
@@ -539,7 +564,7 @@ def _check_proxy_ready(self, timeout=15):
539564
last_error = str(e)
540565
logging.debug(f"Proxy not ready yet: {last_error}")
541566

542-
time.sleep(1)
567+
time.sleep(0.01)
543568

544569
# If we get here, the proxy didn't become ready in time
545570
if self.v2ray_process.poll() is not None:
@@ -776,7 +801,7 @@ def _terminate_windows_process(self, pid, timeout):
776801
# Fallback to subprocess if psutil not available
777802
try:
778803
subprocess.run(["taskkill", "/F", "/T", "/PID", str(pid)], check=False, capture_output=True, timeout=timeout)
779-
time.sleep(0.1)
804+
time.sleep(0.01)
780805
if self.v2ray_process.poll() is not None:
781806
self._process_terminated.set()
782807
return True
@@ -929,7 +954,7 @@ def usage_memory(self):
929954
@property
930955
def usage_memory_mb(self):
931956
"""Get the memory usage of the V2Ray process in MB."""
932-
return self.memory_usage / (1024 * 1024)
957+
return self.usage_memory / (1024 * 1024)
933958

934959
@property
935960
def usage_cpu(self):
@@ -1144,8 +1169,7 @@ def restart_proxy(self, proxy_id):
11441169
# Stop the proxy
11451170
self.proxies[proxy_id].stop()
11461171

1147-
# Wait a bit to ensure clean shutdown
1148-
time.sleep(1)
1172+
time.sleep(0.02)
11491173

11501174
# Start the proxy again
11511175
self.proxies[proxy_id].start()

0 commit comments

Comments
 (0)