You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

110 lines
4.5 KiB

#!/usr/bin/env python3
"""Запуск стенда: главный namespace используется только для чтения снимка."""
import argparse
import json
import os
from pathlib import Path
import shutil
import signal
import subprocess
import sys
import time
from network import Network
from probe import Probes
from topology import manifest
def host_snapshot():
result = {}
for item, arguments in (("addresses", ["ip", "-j", "addr"]), ("routes", ["ip", "-j", "route", "show", "table", "all"]),
("rules", ["ip", "-j", "rule"]), ("qdisc", ["tc", "-j", "qdisc"])):
result[item] = json.loads(subprocess.check_output(arguments, text=True))
return result
def inside(args):
def interrupted(signum, _frame):
raise KeyboardInterrupt(f"signal {signum}")
signal.signal(signal.SIGTERM, interrupted)
signal.signal(signal.SIGINT, interrupted)
directory = Path(args.output)
network = Network(directory, args.seed, args.host_netns)
result = {"pass": False}
probes = Probes(network)
try:
print("Creating 25 isolated network namespaces (16 nodes, 3 routers, 6 NAT gateways)", flush=True)
network.build()
network.snapshot("initial")
print("Calibrating WAN RTT and all 10 NAT clients", flush=True)
probes.start()
calibration = probes.calibrate()
(directory / "calibration.json").write_text(json.dumps(calibration, indent=2) + "\n")
probes.close()
print("Network calibration passed", flush=True)
result["pass"] = True
except BaseException as error:
result["error"] = str(error)
print(f"FAIL: {type(error).__name__}: {error}", file=sys.stderr, flush=True)
finally:
probes.close()
try:
network.snapshot("final")
except Exception as error:
result["snapshot_error"] = str(error)
network.close()
(directory / "result.json").write_text(json.dumps(result, indent=2) + "\n")
return 0 if result["pass"] else 1
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", help="New directory for logs and results")
parser.add_argument("--seed", type=int, default=20261011)
parser.add_argument("--network-only", action="store_true", help="Only topology, WAN and NAT calibration")
parser.add_argument("--inside", action="store_true", help=argparse.SUPPRESS)
parser.add_argument("--host-netns", help=argparse.SUPPRESS)
args = parser.parse_args()
if args.inside:
return inside(args)
for program in ("ip", "tc", "nft", "ethtool", "sysctl", "unshare", "nsenter"):
if not shutil.which(program):
parser.error(f"Required tool is missing: {program}")
directory = Path(args.output or Path(__file__).parent / "runs" / time.strftime("%Y%m%d-%H%M%S")).resolve()
directory.mkdir(parents=True, exist_ok=False)
(directory / "topology.json").write_text(json.dumps(manifest(args.seed), indent=2) + "\n")
original_namespace = os.readlink("/proc/self/ns/net")
before = host_snapshot()
(directory / "host-before.json").write_text(json.dumps(before, indent=2) + "\n")
command = ["unshare", "--user", "--map-root-user", "--keep-caps", "--mount", "--net", "--pid", "--fork",
"--mount-proc", "--propagation", "private", "--kill-child", "--", sys.executable,
str(Path(__file__).resolve()), "--inside", "--host-netns", original_namespace,
"--output", str(directory), "--seed", str(args.seed)]
if args.network_only:
command.append("--network-only")
print(f"Artifacts: {directory}", flush=True)
child = subprocess.Popen(command)
try:
status = child.wait()
except KeyboardInterrupt:
child.terminate()
try:
status = child.wait(timeout=10)
except subprocess.TimeoutExpired:
child.kill()
status = child.wait()
after = host_snapshot()
(directory / "host-after.json").write_text(json.dumps(after, indent=2) + "\n")
unchanged = before == after and os.readlink("/proc/self/ns/net") == original_namespace
(directory / "host-check.json").write_text(json.dumps({"unchanged": unchanged}, indent=2) + "\n")
if not unchanged:
print("FAIL: main network namespace snapshot changed during the run", file=sys.stderr)
return 1
print(f"Main network namespace unchanged; exit={status}", flush=True)
return status
if __name__ == "__main__":
sys.exit(main())