|
@@ -21,12 +21,98 @@ TCP_WIN_RE = re.compile(
|
|
|
UDP_WIN_RE = re.compile(
|
|
UDP_WIN_RE = re.compile(
|
|
|
r"udp flow=(?P<flow>\d+) winner=(?P<winner>\S+) target=(?P<host>[^:]+):(?P<port>\d+)"
|
|
r"udp flow=(?P<flow>\d+) winner=(?P<winner>\S+) target=(?P<host>[^:]+):(?P<port>\d+)"
|
|
|
)
|
|
)
|
|
|
|
|
+UDP_SOCKS_SUMMARY_RE = re.compile(
|
|
|
|
|
+ r"udp summary bind=(?P<bind>\S+) flows=(?P<flows>\d+) winners=(?P<winners>\d+) "
|
|
|
|
|
+ r"winner_breakdown=direct=(?P<direct>\d+),relay=(?P<relay>\d+) "
|
|
|
|
|
+ r"sample=(?P<sample>.*?) candidates=(?P<candidates>\[.*?\]) "
|
|
|
|
|
+ r"sent=(?P<sent>\d+) recv=(?P<recv>\d+) dup=(?P<dup>\d+) "
|
|
|
|
|
+ r"direct_paths=(?P<direct_paths>\d+) relay_paths=(?P<relay_paths>\d+) relay_errors=(?P<relay_errors>.*)"
|
|
|
|
|
+)
|
|
|
|
|
|
|
|
|
|
|
|
|
def normalize_winner(name: str) -> str:
|
|
def normalize_winner(name: str) -> str:
|
|
|
return "direct" if name.startswith("direct") else name
|
|
return "direct" if name.startswith("direct") else name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def parse_summary_log(log_path: Path) -> dict[str, object]:
|
|
|
|
|
+ tcp_total = 0
|
|
|
|
|
+ tcp_direct = 0
|
|
|
|
|
+ tcp_relay = 0
|
|
|
|
|
+ tcp_winners: dict[str, int] = defaultdict(int)
|
|
|
|
|
+ tcp_targets: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
|
|
|
|
|
+
|
|
|
|
|
+ udp_total = 0
|
|
|
|
|
+ udp_direct = 0
|
|
|
|
|
+ udp_relay = 0
|
|
|
|
|
+ udp_winners: dict[str, int] = defaultdict(int)
|
|
|
|
|
+ udp_targets: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
|
|
|
|
|
+ udp_snapshot: dict[str, object] | None = None
|
|
|
|
|
+
|
|
|
|
|
+ for line in log_path.read_text(errors="replace").splitlines():
|
|
|
|
|
+ tcp_match = TCP_WIN_RE.search(line)
|
|
|
|
|
+ if tcp_match:
|
|
|
|
|
+ tcp_total += 1
|
|
|
|
|
+ winner = normalize_winner(tcp_match.group("winner"))
|
|
|
|
|
+ host = tcp_match.group("host")
|
|
|
|
|
+ port = tcp_match.group("port")
|
|
|
|
|
+ key = f"{host}:{port}"
|
|
|
|
|
+ tcp_winners[winner] += 1
|
|
|
|
|
+ tcp_targets[key][winner] += 1
|
|
|
|
|
+ if winner == "direct":
|
|
|
|
|
+ tcp_direct += 1
|
|
|
|
|
+ else:
|
|
|
|
|
+ tcp_relay += 1
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ udp_match = UDP_WIN_RE.search(line)
|
|
|
|
|
+ if udp_match:
|
|
|
|
|
+ udp_total += 1
|
|
|
|
|
+ winner = normalize_winner(udp_match.group("winner"))
|
|
|
|
|
+ host = udp_match.group("host")
|
|
|
|
|
+ port = udp_match.group("port")
|
|
|
|
|
+ key = f"{host}:{port}"
|
|
|
|
|
+ udp_winners[winner] += 1
|
|
|
|
|
+ udp_targets[key][winner] += 1
|
|
|
|
|
+ if winner == "direct":
|
|
|
|
|
+ udp_direct += 1
|
|
|
|
|
+ else:
|
|
|
|
|
+ udp_relay += 1
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ udp_snapshot_match = UDP_SOCKS_SUMMARY_RE.search(line)
|
|
|
|
|
+ if udp_snapshot_match:
|
|
|
|
|
+ udp_snapshot = {
|
|
|
|
|
+ "bind": udp_snapshot_match.group("bind"),
|
|
|
|
|
+ "flows": int(udp_snapshot_match.group("flows")),
|
|
|
|
|
+ "winners": int(udp_snapshot_match.group("winners")),
|
|
|
|
|
+ "direct": int(udp_snapshot_match.group("direct")),
|
|
|
|
|
+ "relay": int(udp_snapshot_match.group("relay")),
|
|
|
|
|
+ "sample": udp_snapshot_match.group("sample"),
|
|
|
|
|
+ "candidates": udp_snapshot_match.group("candidates"),
|
|
|
|
|
+ "sent": int(udp_snapshot_match.group("sent")),
|
|
|
|
|
+ "recv": int(udp_snapshot_match.group("recv")),
|
|
|
|
|
+ "dup": int(udp_snapshot_match.group("dup")),
|
|
|
|
|
+ "direct_paths": int(udp_snapshot_match.group("direct_paths")),
|
|
|
|
|
+ "relay_paths": int(udp_snapshot_match.group("relay_paths")),
|
|
|
|
|
+ "relay_errors": udp_snapshot_match.group("relay_errors"),
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ "path": log_path,
|
|
|
|
|
+ "tcp_total": tcp_total,
|
|
|
|
|
+ "tcp_direct": tcp_direct,
|
|
|
|
|
+ "tcp_relay": tcp_relay,
|
|
|
|
|
+ "tcp_winners": tcp_winners,
|
|
|
|
|
+ "tcp_targets": tcp_targets,
|
|
|
|
|
+ "udp_total": udp_total,
|
|
|
|
|
+ "udp_direct": udp_direct,
|
|
|
|
|
+ "udp_relay": udp_relay,
|
|
|
|
|
+ "udp_winners": udp_winners,
|
|
|
|
|
+ "udp_targets": udp_targets,
|
|
|
|
|
+ "udp_snapshot": udp_snapshot,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
|
parser = argparse.ArgumentParser(prog="mynetspeeder")
|
|
parser = argparse.ArgumentParser(prog="mynetspeeder")
|
|
|
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
@@ -98,48 +184,34 @@ def handle_summary(args: argparse.Namespace) -> int:
|
|
|
log_path = Path(args.log_file)
|
|
log_path = Path(args.log_file)
|
|
|
if not log_path.exists():
|
|
if not log_path.exists():
|
|
|
raise SystemExit(f"log file not found: {log_path}")
|
|
raise SystemExit(f"log file not found: {log_path}")
|
|
|
-
|
|
|
|
|
- tcp_total = 0
|
|
|
|
|
- tcp_direct = 0
|
|
|
|
|
- tcp_relay = 0
|
|
|
|
|
- tcp_winners: dict[str, int] = defaultdict(int)
|
|
|
|
|
- tcp_targets: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
|
|
|
|
|
-
|
|
|
|
|
- udp_total = 0
|
|
|
|
|
- udp_direct = 0
|
|
|
|
|
- udp_relay = 0
|
|
|
|
|
- udp_winners: dict[str, int] = defaultdict(int)
|
|
|
|
|
- udp_targets: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
|
|
|
|
|
-
|
|
|
|
|
- for line in log_path.read_text(errors="replace").splitlines():
|
|
|
|
|
- tcp_match = TCP_WIN_RE.search(line)
|
|
|
|
|
- if tcp_match:
|
|
|
|
|
- tcp_total += 1
|
|
|
|
|
- winner = normalize_winner(tcp_match.group("winner"))
|
|
|
|
|
- host = tcp_match.group("host")
|
|
|
|
|
- port = tcp_match.group("port")
|
|
|
|
|
- key = f"{host}:{port}"
|
|
|
|
|
- tcp_winners[winner] += 1
|
|
|
|
|
- tcp_targets[key][winner] += 1
|
|
|
|
|
- if winner == "direct":
|
|
|
|
|
- tcp_direct += 1
|
|
|
|
|
- else:
|
|
|
|
|
- tcp_relay += 1
|
|
|
|
|
- continue
|
|
|
|
|
-
|
|
|
|
|
- udp_match = UDP_WIN_RE.search(line)
|
|
|
|
|
- if udp_match:
|
|
|
|
|
- udp_total += 1
|
|
|
|
|
- winner = normalize_winner(udp_match.group("winner"))
|
|
|
|
|
- host = udp_match.group("host")
|
|
|
|
|
- port = udp_match.group("port")
|
|
|
|
|
- key = f"{host}:{port}"
|
|
|
|
|
- udp_winners[winner] += 1
|
|
|
|
|
- udp_targets[key][winner] += 1
|
|
|
|
|
- if winner == "direct":
|
|
|
|
|
- udp_direct += 1
|
|
|
|
|
- else:
|
|
|
|
|
- udp_relay += 1
|
|
|
|
|
|
|
+ parsed = parse_summary_log(log_path)
|
|
|
|
|
+ tcp_total = parsed["tcp_total"]
|
|
|
|
|
+ tcp_direct = parsed["tcp_direct"]
|
|
|
|
|
+ tcp_relay = parsed["tcp_relay"]
|
|
|
|
|
+ tcp_winners = parsed["tcp_winners"]
|
|
|
|
|
+ tcp_targets = parsed["tcp_targets"]
|
|
|
|
|
+ udp_total = parsed["udp_total"]
|
|
|
|
|
+ udp_direct = parsed["udp_direct"]
|
|
|
|
|
+ udp_relay = parsed["udp_relay"]
|
|
|
|
|
+ udp_winners = parsed["udp_winners"]
|
|
|
|
|
+ udp_targets = parsed["udp_targets"]
|
|
|
|
|
+ udp_snapshot = parsed["udp_snapshot"]
|
|
|
|
|
+
|
|
|
|
|
+ if udp_total == 0:
|
|
|
|
|
+ sibling_logs = [log_path.with_name("mynetspeeder-socks.log"), log_path.with_name("mynetspeeder-relay.log")]
|
|
|
|
|
+ for sibling in sibling_logs:
|
|
|
|
|
+ if not sibling.exists():
|
|
|
|
|
+ continue
|
|
|
|
|
+ sibling_parsed = parse_summary_log(sibling)
|
|
|
|
|
+ if sibling_parsed["udp_total"] == 0 and sibling_parsed["udp_snapshot"] is None:
|
|
|
|
|
+ continue
|
|
|
|
|
+ udp_total = sibling_parsed["udp_total"]
|
|
|
|
|
+ udp_direct = sibling_parsed["udp_direct"]
|
|
|
|
|
+ udp_relay = sibling_parsed["udp_relay"]
|
|
|
|
|
+ udp_winners = sibling_parsed["udp_winners"]
|
|
|
|
|
+ udp_targets = sibling_parsed["udp_targets"]
|
|
|
|
|
+ udp_snapshot = sibling_parsed["udp_snapshot"]
|
|
|
|
|
+ break
|
|
|
|
|
|
|
|
tcp_ordered_targets = sorted(
|
|
tcp_ordered_targets = sorted(
|
|
|
tcp_targets.items(),
|
|
tcp_targets.items(),
|
|
@@ -179,9 +251,34 @@ def handle_summary(args: argparse.Namespace) -> int:
|
|
|
}
|
|
}
|
|
|
for target, counts in udp_ordered_targets
|
|
for target, counts in udp_ordered_targets
|
|
|
],
|
|
],
|
|
|
|
|
+ "snapshot": udp_snapshot,
|
|
|
},
|
|
},
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if udp_total == 0 and udp_snapshot is not None:
|
|
|
|
|
+ result["udp"]["total"] = udp_snapshot["winners"]
|
|
|
|
|
+ result["udp"]["direct"] = udp_snapshot["direct"]
|
|
|
|
|
+ result["udp"]["relay"] = udp_snapshot["relay"]
|
|
|
|
|
+ if not result["udp"]["winners"] and udp_snapshot is not None:
|
|
|
|
|
+ snapshot_winners: dict[str, int] = {}
|
|
|
|
|
+ if int(udp_snapshot["direct"]) > 0:
|
|
|
|
|
+ snapshot_winners["direct"] = int(udp_snapshot["direct"])
|
|
|
|
|
+ if int(udp_snapshot["relay"]) > 0:
|
|
|
|
|
+ snapshot_winners["relay"] = int(udp_snapshot["relay"])
|
|
|
|
|
+ result["udp"]["winners"] = snapshot_winners
|
|
|
|
|
+ if not result["udp"]["targets"] and udp_snapshot is not None:
|
|
|
|
|
+ snapshot_target_wins: dict[str, int] = {}
|
|
|
|
|
+ if int(udp_snapshot["direct"]) > 0:
|
|
|
|
|
+ snapshot_target_wins["direct"] = int(udp_snapshot["direct"])
|
|
|
|
|
+ if int(udp_snapshot["relay"]) > 0:
|
|
|
|
|
+ snapshot_target_wins["relay"] = int(udp_snapshot["relay"])
|
|
|
|
|
+ result["udp"]["targets"] = [
|
|
|
|
|
+ {
|
|
|
|
|
+ "target": f"snapshot@{udp_snapshot['bind']}",
|
|
|
|
|
+ "wins": snapshot_target_wins,
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
if args.json:
|
|
if args.json:
|
|
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
|
return 0
|
|
return 0
|
|
@@ -190,6 +287,13 @@ def handle_summary(args: argparse.Namespace) -> int:
|
|
|
for protocol in ("tcp", "udp"):
|
|
for protocol in ("tcp", "udp"):
|
|
|
section = result[protocol]
|
|
section = result[protocol]
|
|
|
print(f"{protocol}: total={section['total']} direct={section['direct']} relay={section['relay']}")
|
|
print(f"{protocol}: total={section['total']} direct={section['direct']} relay={section['relay']}")
|
|
|
|
|
+ if protocol == "udp" and section.get("snapshot"):
|
|
|
|
|
+ snapshot = section["snapshot"]
|
|
|
|
|
+ print(
|
|
|
|
|
+ f" snapshot: bind={snapshot['bind']} flows={snapshot['flows']} winners={snapshot['winners']} "
|
|
|
|
|
+ f"direct_paths={snapshot['direct_paths']} relay_paths={snapshot['relay_paths']} dup={snapshot['dup']} "
|
|
|
|
|
+ f"candidates={snapshot['candidates']}"
|
|
|
|
|
+ )
|
|
|
print("winners:")
|
|
print("winners:")
|
|
|
for name, count in section["winners"].items():
|
|
for name, count in section["winners"].items():
|
|
|
print(f" {name}: {count}")
|
|
print(f" {name}: {count}")
|