cli.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. from __future__ import annotations
  2. import argparse
  3. import asyncio
  4. import json
  5. import re
  6. from collections import defaultdict
  7. from pathlib import Path
  8. from . import __version__
  9. from .config import Config
  10. from .relay_server import RelayServer
  11. from .relay_client import RelayManager
  12. from .transparent_edge import TransparentEdge
  13. WIN_RE = re.compile(
  14. r"tcp win session=(?P<session>\d+) target=(?P<host>[^:]+):(?P<port>\d+) winner=(?P<winner>\S+) .*?direct=(?P<direct>\d+) .*?relay=(?P<relay>\d+)"
  15. )
  16. def build_parser() -> argparse.ArgumentParser:
  17. parser = argparse.ArgumentParser(prog="mynetspeeder")
  18. parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
  19. sub = parser.add_subparsers(dest="command", required=True)
  20. relay = sub.add_parser("relay", help="在子节点 VPS 上启动 relay")
  21. relay.add_argument("--listen-host", default="0.0.0.0")
  22. relay.add_argument("--listen-port", type=int, default=9009)
  23. relay.add_argument("--token", required=True)
  24. relay.set_defaults(handler=handle_relay)
  25. edge = sub.add_parser("edge", help="在当前主 VPS 上启动透明 direct 出站加速")
  26. edge.add_argument("--listen-host", default="127.0.0.1")
  27. edge.add_argument("--listen-port", type=int, default=19080)
  28. edge.add_argument("--config", required=True)
  29. edge.add_argument("--enable-udp", action="store_true")
  30. edge.set_defaults(handler=handle_edge)
  31. probe = sub.add_parser("probe", help="查看子节点探测与在线状态")
  32. probe.add_argument("--config", required=True)
  33. probe.add_argument("--once", action="store_true")
  34. probe.set_defaults(handler=handle_probe)
  35. summary = sub.add_parser("summary", help="汇总透明模式日志里的胜率")
  36. summary.add_argument("--log-file", default="/var/log/mynetspeeder-edge.log")
  37. summary.add_argument("--top", type=int, default=10)
  38. summary.add_argument("--json", action="store_true")
  39. summary.set_defaults(handler=handle_summary)
  40. return parser
  41. def handle_relay(args: argparse.Namespace) -> int:
  42. asyncio.run(RelayServer(args.token).start(args.listen_host, args.listen_port))
  43. return 0
  44. def handle_edge(args: argparse.Namespace) -> int:
  45. asyncio.run(TransparentEdge(args.listen_host, args.listen_port, Config.load(args.config), enable_udp=args.enable_udp).start())
  46. return 0
  47. def handle_probe(args: argparse.Namespace) -> int:
  48. async def run_probe() -> None:
  49. manager = RelayManager(Config.load(args.config))
  50. await manager.start()
  51. await asyncio.sleep(2)
  52. print(json.dumps(manager.snapshot(), ensure_ascii=False, indent=2))
  53. if not args.once:
  54. while True:
  55. await asyncio.sleep(5)
  56. print(json.dumps(manager.snapshot(), ensure_ascii=False, indent=2))
  57. asyncio.run(run_probe())
  58. return 0
  59. def handle_summary(args: argparse.Namespace) -> int:
  60. log_path = Path(args.log_file)
  61. if not log_path.exists():
  62. raise SystemExit(f"log file not found: {log_path}")
  63. total = 0
  64. direct = 0
  65. relay = 0
  66. winners: dict[str, int] = defaultdict(int)
  67. targets: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
  68. for line in log_path.read_text(errors="replace").splitlines():
  69. match = WIN_RE.search(line)
  70. if not match:
  71. continue
  72. total += 1
  73. winner = match.group("winner")
  74. host = match.group("host")
  75. port = match.group("port")
  76. key = f"{host}:{port}"
  77. winners[winner] += 1
  78. targets[key][winner] += 1
  79. if winner == "direct":
  80. direct += 1
  81. else:
  82. relay += 1
  83. ordered_targets = sorted(
  84. targets.items(),
  85. key=lambda item: sum(item[1].values()),
  86. reverse=True,
  87. )[: max(args.top, 0)]
  88. result = {
  89. "log_file": str(log_path),
  90. "total": total,
  91. "direct": direct,
  92. "relay": relay,
  93. "winners": dict(sorted(winners.items(), key=lambda item: (-item[1], item[0]))),
  94. "targets": [
  95. {
  96. "target": target,
  97. "wins": dict(sorted(counts.items(), key=lambda item: (-item[1], item[0]))),
  98. }
  99. for target, counts in ordered_targets
  100. ],
  101. }
  102. if args.json:
  103. print(json.dumps(result, ensure_ascii=False, indent=2))
  104. return 0
  105. print(f"log: {log_path}")
  106. print(f"total: {total} direct: {direct} relay: {relay}")
  107. print("winners:")
  108. for name, count in result["winners"].items():
  109. print(f" {name}: {count}")
  110. print("targets:")
  111. for item in result["targets"]:
  112. wins = ", ".join(f"{name}={count}" for name, count in item["wins"].items())
  113. print(f" {item['target']}: {wins}")
  114. return 0
  115. def main() -> int:
  116. parser = build_parser()
  117. args = parser.parse_args()
  118. return args.handler(args)