diff --git a/tests/stream_netns/nat_scenario.py b/tests/stream_netns/nat_scenario.py index 9fcc42c4..54470b2b 100644 --- a/tests/stream_netns/nat_scenario.py +++ b/tests/stream_netns/nat_scenario.py @@ -64,7 +64,11 @@ def check_profiles(scenario): print(f"NAT routed private helper: {private_helper.name} through {second.name}, group={actual['group']}", flush=True) verified = {} + protocols = ["tcp"] if scenario.args.tcp_only else ["udp", "tcp"] if scenario.args.tcp else ["udp"] for owner in public + [node for node in NODES if node.nat]: + verified[owner.name] = {} + if scenario.args.tcp_only: + continue observer = second if owner.name == first.name else first if not scenario.args.tcp_only: agents[owner.name].rpc("nat_check", socket="udp", observer=agents[observer.name].identity) @@ -80,45 +84,46 @@ def check_profiles(scenario): connect(observer, helper) connect(owner, observer) store_owner(owner, observer) - protocols = ["tcp"] if scenario.args.tcp_only else ["udp", "tcp"] if scenario.args.tcp else ["udp"] - observed = {} - for protocol in protocols: - if protocol == "tcp": - # Только один действующий TCP-пир, без испытания входящих и без сравнения портов. - connect(owner, observer, "tcp") - data = scenario.wait(f"nat-tcp-ip-{owner.name}", lambda statistics: - profile(statistics[owner.name], "tcp")["source"] == "ETCP" and - bool(profile(statistics[owner.name], "tcp")["external"]), 10) - actual = profile(data[owner.name], "tcp") - expected = ("UNKNOWN" if owner.nat else "UNCHANGED", "UNKNOWN") - if actual["rounds"] or int(actual["helper"]): - raise RuntimeError(f"{owner.name}: TCP IP detection unexpectedly used a helper round: {actual}") - else: - actual = measure(owner, observer, protocol) - if not owner.nat: - expected = ("UNCHANGED", "OPEN") - else: - gateway = next(item for item in NATS if item.name == owner.nat) - expected = {"cone": ("EIM", "OPEN"), "restricted": ("EIM", "CONTACT_REQUIRED"), - "strict": ("DEPENDENT", "UNKNOWN")}[gateway.mode] - if (actual["mapping"], actual["inbound"]) != expected: - raise RuntimeError(f"{owner.name}/{protocol}: expected {expected}, detector returned {actual}") - if protocol == "tcp": - ip = next(item for item in NATS if item.name == owner.nat).ip if owner.nat else owner.ip - external = f"{ip}:0" - elif expected[0] == "EIM": - gateway = next(item for item in NATS if item.name == owner.nat) - external = f"{gateway.ip}:{owner.mapped_port}" - elif expected[0] == "UNCHANGED": - external = f"{owner.ip}:32000" - else: - external = "" - if actual["external"] != external: - raise RuntimeError(f"{owner.name}/{protocol}: foreign/invalid external endpoint: {actual}, expected {external}") - observed[protocol] = actual - print(f"NAT {owner.name}/{protocol}: {actual['mapping']} / {actual['inbound']}" - f" external={actual['external'] or '-'} reason={actual['reason']}", flush=True) - verified[owner.name] = observed + actual = measure(owner, observer, "udp") + if not owner.nat: + expected = ("UNCHANGED", "OPEN") + else: + gateway = next(item for item in NATS if item.name == owner.nat) + expected = {"cone": ("EIM", "OPEN"), "restricted": ("EIM", "CONTACT_REQUIRED"), + "strict": ("DEPENDENT", "UNKNOWN")}[gateway.mode] + if (actual["mapping"], actual["inbound"]) != expected: + raise RuntimeError(f"{owner.name}/udp: expected {expected}, detector returned {actual}") + if expected[0] == "EIM": + gateway = next(item for item in NATS if item.name == owner.nat) + external = f"{gateway.ip}:{owner.mapped_port}" + elif expected[0] == "UNCHANGED": + external = f"{owner.ip}:32000" + else: + external = "" + if actual["external"] != external: + raise RuntimeError(f"{owner.name}/udp: foreign/invalid external endpoint: {actual}, expected {external}") + verified[owner.name]["udp"] = actual + print(f"NAT {owner.name}/udp: {actual['mapping']} / {actual['inbound']}" + f" external={actual['external'] or '-'} reason={actual['reason']}", flush=True) + (scenario.network.directory / "nat-profiles.json").write_text(json.dumps(verified, indent=2) + "\n") + + # Дополнительные TCP-линки создаются после холодных UDP-измерений: NCD может запустить и UDP-попытки. + if "tcp" in protocols: + for owner in public + [node for node in NODES if node.nat]: + observer = second if owner.name == first.name else first + connect(owner, observer, "tcp") + data = scenario.wait(f"nat-tcp-ip-{owner.name}", lambda statistics: + profile(statistics[owner.name], "tcp")["source"] == "ETCP" and + bool(profile(statistics[owner.name], "tcp")["external"]), 10) + actual = profile(data[owner.name], "tcp") + expected = ("UNKNOWN" if owner.nat else "UNCHANGED", "UNKNOWN") + ip = next(item for item in NATS if item.name == owner.nat).ip if owner.nat else owner.ip + if (actual["mapping"], actual["inbound"], actual["external"]) != (*expected, f"{ip}:0"): + raise RuntimeError(f"{owner.name}/tcp: expected {expected}, IP={ip}:0, detector returned {actual}") + if actual["rounds"] or int(actual["helper"]): + raise RuntimeError(f"{owner.name}: TCP IP detection unexpectedly used a helper round: {actual}") + verified[owner.name]["tcp"] = actual + print(f"NAT {owner.name}/tcp: {actual['mapping']} / {actual['inbound']} external={actual['external']}", flush=True) (scenario.network.directory / "nat-profiles.json").write_text(json.dumps(verified, indent=2) + "\n") # Повтор на использовавшемся порту сохраняет mapping, но не обновляет OPEN