|
|
@@ -16,10 +16,13 @@ from .transparent_edge import TransparentEdge
|
|
|
|
|
|
|
|
|
TCP_WIN_RE = re.compile(
|
|
|
- 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+)"
|
|
|
+ r"tcp win session=(?P<session>\d+) target=(?P<target>\S+) winner=(?P<winner>\S+) .*?direct=(?P<direct>\d+) .*?relay=(?P<relay>\d+) .*?family=(?P<family>ipv4|ipv6)"
|
|
|
+)
|
|
|
+RELAY_STATUS_SUMMARY_RE = re.compile(
|
|
|
+ r"relay status summary name=(?P<name>\S+) errors=(?P<errors>\d+) last_detail=(?P<detail>.*)"
|
|
|
)
|
|
|
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<target>\S+)"
|
|
|
)
|
|
|
SOCKS_UDP_SUMMARY_RE = re.compile(
|
|
|
r"udp summary .*?winner_detail=(?P<winner_detail>.*?) (?:target_detail=(?P<target_detail>.*?) )?packets_sent="
|
|
|
@@ -30,6 +33,15 @@ def normalize_winner(name: str) -> str:
|
|
|
return "direct" if name.startswith("direct") else name
|
|
|
|
|
|
|
|
|
+def split_target(target: str) -> tuple[str, str] | None:
|
|
|
+ if ":" not in target:
|
|
|
+ return None
|
|
|
+ host, port = target.rsplit(":", 1)
|
|
|
+ if not port.isdigit():
|
|
|
+ return None
|
|
|
+ return host, port
|
|
|
+
|
|
|
+
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
|
parser = argparse.ArgumentParser(prog="mynetspeeder")
|
|
|
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
|
@@ -109,6 +121,9 @@ def handle_summary(args: argparse.Namespace) -> int:
|
|
|
tcp_relay = 0
|
|
|
tcp_winners: dict[str, int] = defaultdict(int)
|
|
|
tcp_targets: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
|
|
|
+ tcp_families: dict[str, int] = defaultdict(int)
|
|
|
+ relay_status_errors: dict[str, int] = defaultdict(int)
|
|
|
+ relay_status_details: dict[str, str] = {}
|
|
|
|
|
|
udp_total = 0
|
|
|
udp_direct = 0
|
|
|
@@ -117,15 +132,25 @@ def handle_summary(args: argparse.Namespace) -> int:
|
|
|
udp_targets: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
|
|
|
|
|
|
for line in log_path.read_text(errors="replace").splitlines():
|
|
|
+ relay_status_match = RELAY_STATUS_SUMMARY_RE.search(line)
|
|
|
+ if relay_status_match:
|
|
|
+ name = relay_status_match.group("name")
|
|
|
+ relay_status_errors[name] += int(relay_status_match.group("errors"))
|
|
|
+ relay_status_details[name] = relay_status_match.group("detail")
|
|
|
+ continue
|
|
|
+
|
|
|
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")
|
|
|
+ target_parts = split_target(tcp_match.group("target"))
|
|
|
+ if target_parts is None:
|
|
|
+ continue
|
|
|
+ host, port = target_parts
|
|
|
key = f"{host}:{port}"
|
|
|
+ tcp_total += 1
|
|
|
tcp_winners[winner] += 1
|
|
|
tcp_targets[key][winner] += 1
|
|
|
+ tcp_families[tcp_match.group("family")] += 1
|
|
|
if winner == "direct":
|
|
|
tcp_direct += 1
|
|
|
else:
|
|
|
@@ -134,11 +159,13 @@ def handle_summary(args: argparse.Namespace) -> int:
|
|
|
|
|
|
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")
|
|
|
+ target_parts = split_target(udp_match.group("target"))
|
|
|
+ if target_parts is None:
|
|
|
+ continue
|
|
|
+ host, port = target_parts
|
|
|
key = f"{host}:{port}"
|
|
|
+ udp_total += 1
|
|
|
udp_winners[winner] += 1
|
|
|
udp_targets[key][winner] += 1
|
|
|
if winner == "direct":
|
|
|
@@ -205,6 +232,7 @@ def handle_summary(args: argparse.Namespace) -> int:
|
|
|
"total": tcp_total,
|
|
|
"direct": tcp_direct,
|
|
|
"relay": tcp_relay,
|
|
|
+ "families": dict(sorted(tcp_families.items(), key=lambda item: item[0])),
|
|
|
"winners": dict(sorted(tcp_winners.items(), key=lambda item: (-item[1], item[0]))),
|
|
|
"targets": [
|
|
|
{
|
|
|
@@ -227,6 +255,10 @@ def handle_summary(args: argparse.Namespace) -> int:
|
|
|
for target, counts in udp_ordered_targets
|
|
|
],
|
|
|
},
|
|
|
+ "relay_status": {
|
|
|
+ "errors": dict(sorted(relay_status_errors.items(), key=lambda item: (-item[1], item[0]))),
|
|
|
+ "last_detail": dict(sorted(relay_status_details.items(), key=lambda item: item[0])),
|
|
|
+ },
|
|
|
}
|
|
|
|
|
|
if args.json:
|
|
|
@@ -239,6 +271,9 @@ def handle_summary(args: argparse.Namespace) -> int:
|
|
|
for protocol in ("tcp", "udp"):
|
|
|
section = result[protocol]
|
|
|
print(f"{protocol}: total={section['total']} direct={section['direct']} relay={section['relay']}")
|
|
|
+ if protocol == "tcp":
|
|
|
+ families = ", ".join(f"{name}={count}" for name, count in section.get("families", {}).items()) or "none"
|
|
|
+ print(f"families: {families}")
|
|
|
print("winners:")
|
|
|
for name, count in section["winners"].items():
|
|
|
print(f" {name}: {count}")
|
|
|
@@ -246,6 +281,11 @@ def handle_summary(args: argparse.Namespace) -> int:
|
|
|
for item in section["targets"]:
|
|
|
wins = ", ".join(f"{name}={count}" for name, count in item["wins"].items())
|
|
|
print(f" {item['target']}: {wins}")
|
|
|
+ if result["relay_status"]["errors"]:
|
|
|
+ print("relay_status:")
|
|
|
+ for name, count in result["relay_status"]["errors"].items():
|
|
|
+ detail = result["relay_status"]["last_detail"].get(name, "unknown")
|
|
|
+ print(f" {name}: errors={count} last_detail={detail}")
|
|
|
return 0
|
|
|
|
|
|
|