ci(interop-netem): adopt nsenter for improved network namespace operations
Rust CI / cargo fmt (push) Successful in 17s
Rust CI / cargo clippy (push) Successful in 28s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 26s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Successful in 31s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 1m12s
Rust CI / cargo doc (push) Successful in 29s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Successful in 1m20s
Fuzz Smoke / cargo fuzz smoke (push) Successful in 1m38s
Netem Interop / netem interop (push) Successful in 1m20s
Supply chain / cargo-deny advisories (push) Successful in 18s
Supply chain / cargo-deny bans licenses sources (push) Successful in 17s
Supply chain / actionlint (push) Successful in 2s
Build binaries / Build (arm-musleabi) (push) Successful in 4m37s
Build binaries / Build (aarch64-musl) (push) Successful in 4m50s
Build binaries / Build (arm-musleabihf) (push) Successful in 4m9s
Build binaries / Build (armv7-musleabi) (push) Successful in 4m7s
Build binaries / Build (armv7-musleabihf) (push) Successful in 4m19s
Build binaries / Build (i686-musl) (push) Successful in 4m24s
Build binaries / Build (mips-musl) (push) Successful in 3m58s
Build binaries / Build (mips64-muslabi64) (push) Successful in 4m4s
Build binaries / Build (mips64el-muslabi64) (push) Successful in 4m1s
Build binaries / Build (mipsel-musl) (push) Successful in 4m2s
Build binaries / Build (riscv64gc-musl) (push) Successful in 4m13s
Build binaries / Build (x86_64-musl) (push) Successful in 4m8s

This commit is contained in:
Nils Andreas Svee committed 2026-10-26 16:48:41 +07:00
1 parent 492f33408c
commit 3344407706
2 files changed
+27 -28

No files matched your search

+4 -2
View File
@@ -43,12 +43,13 @@ jobs:
- name: Install netem dependencies - name: Install netem dependencies
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends iproute2 python3 sudo apt-get install -y --no-install-recommends iproute2 python3 util-linux
- name: Check host tools and network administration access - name: Check host tools and network administration access
run: | run: |
ip -V ip -V
tc -V tc -V
nsenter --version
go version go version
# Exercise the namespace, veth, and qdisc operations the harness needs. # Exercise the namespace, veth, and qdisc operations the harness needs.
# sudo/NET_ADMIN alone do not establish mount/namespace permissions. # sudo/NET_ADMIN alone do not establish mount/namespace permissions.
@@ -56,9 +57,10 @@ jobs:
probe_ns="irtt-preflight-$$" probe_ns="irtt-preflight-$$"
ip netns add "$probe_ns" ip netns add "$probe_ns"
trap 'ip netns delete "$probe_ns"' EXIT trap 'ip netns delete "$probe_ns"' EXIT
ip netns exec "$probe_ns" sh -ec ' nsenter --net="/run/netns/$probe_ns" -- sh -ec '
ip link add probe0 type veth peer name probe1 ip link add probe0 type veth peer name probe1
tc qdisc add dev probe0 root netem delay 1ms tc qdisc add dev probe0 root netem delay 1ms
tc qdisc show dev probe0
' '
SH SH
+23 -26
View File
@@ -215,7 +215,7 @@ def select_scenarios(args: argparse.Namespace) -> list[Scenario]:
def preflight(args: argparse.Namespace) -> None: def preflight(args: argparse.Namespace) -> None:
if platform.system() != "Linux": if platform.system() != "Linux":
raise RuntimeError("netem interop requires Linux network namespaces and tc netem") raise RuntimeError("netem interop requires Linux network namespaces and tc netem")
for binary in ["ip", "tc"]: for binary in ["ip", "tc", "nsenter"]:
if shutil.which(binary) is None: if shutil.which(binary) is None:
raise RuntimeError(f"required command not found in PATH: {binary}") raise RuntimeError(f"required command not found in PATH: {binary}")
if args.sudo and shutil.which(args.sudo) is None: if args.sudo and shutil.which(args.sudo) is None:
@@ -256,7 +256,7 @@ def sudo_prefix(args: argparse.Namespace) -> list[str]:
def netns(args: argparse.Namespace, namespace: str, command: list[str]) -> list[str]: def netns(args: argparse.Namespace, namespace: str, command: list[str]) -> list[str]:
return [*sudo_prefix(args), "ip", "netns", "exec", namespace, *command] return [*sudo_prefix(args), "nsenter", f"--net=/run/netns/{namespace}", "--", *command]
def run_checked( def run_checked(
@@ -300,12 +300,12 @@ def setup_namespaces(args: argparse.Namespace, run_dir: Path) -> None:
[*sudo_prefix(args), "ip", "link", "add", CLIENT_VETH, "type", "veth", "peer", "name", SERVER_VETH], [*sudo_prefix(args), "ip", "link", "add", CLIENT_VETH, "type", "veth", "peer", "name", SERVER_VETH],
[*sudo_prefix(args), "ip", "link", "set", CLIENT_VETH, "netns", CLIENT_NS], [*sudo_prefix(args), "ip", "link", "set", CLIENT_VETH, "netns", CLIENT_NS],
[*sudo_prefix(args), "ip", "link", "set", SERVER_VETH, "netns", SERVER_NS], [*sudo_prefix(args), "ip", "link", "set", SERVER_VETH, "netns", SERVER_NS],
[*sudo_prefix(args), "ip", "-n", CLIENT_NS, "addr", "add", CLIENT_ADDR, "dev", CLIENT_VETH], netns(args, CLIENT_NS, ["ip", "addr", "add", CLIENT_ADDR, "dev", CLIENT_VETH]),
[*sudo_prefix(args), "ip", "-n", SERVER_NS, "addr", "add", SERVER_ADDR, "dev", SERVER_VETH], netns(args, SERVER_NS, ["ip", "addr", "add", SERVER_ADDR, "dev", SERVER_VETH]),
[*sudo_prefix(args), "ip", "-n", CLIENT_NS, "link", "set", "lo", "up"], netns(args, CLIENT_NS, ["ip", "link", "set", "lo", "up"]),
[*sudo_prefix(args), "ip", "-n", SERVER_NS, "link", "set", "lo", "up"], netns(args, SERVER_NS, ["ip", "link", "set", "lo", "up"]),
[*sudo_prefix(args), "ip", "-n", CLIENT_NS, "link", "set", CLIENT_VETH, "up"], netns(args, CLIENT_NS, ["ip", "link", "set", CLIENT_VETH, "up"]),
[*sudo_prefix(args), "ip", "-n", SERVER_NS, "link", "set", SERVER_VETH, "up"], netns(args, SERVER_NS, ["ip", "link", "set", SERVER_VETH, "up"]),
] ]
for index, command in enumerate(commands): for index, command in enumerate(commands):
run_checked(command, run_dir / "setup" / f"{index:02d}") run_checked(command, run_dir / "setup" / f"{index:02d}")
@@ -409,12 +409,7 @@ def apply_netem(args: argparse.Namespace, scenario: Scenario, scenario_dir: Path
for namespace, dev, setting in commands: for namespace, dev, setting in commands:
if not setting: if not setting:
continue continue
command = [ command = netns(args, namespace, [
*sudo_prefix(args),
"ip",
"netns",
"exec",
namespace,
"tc", "tc",
"qdisc", "qdisc",
"replace", "replace",
@@ -423,25 +418,20 @@ def apply_netem(args: argparse.Namespace, scenario: Scenario, scenario_dir: Path
"root", "root",
"netem", "netem",
*shlex.split(setting), *shlex.split(setting),
] ])
run_checked(command, scenario_dir / "netem" / f"{namespace}-{dev}") run_checked(command, scenario_dir / "netem" / f"{namespace}-{dev}")
def delete_qdiscs(args: argparse.Namespace, log_dir: Path) -> None: def delete_qdiscs(args: argparse.Namespace, log_dir: Path) -> None:
for namespace, dev in [(CLIENT_NS, CLIENT_VETH), (SERVER_NS, SERVER_VETH)]: for namespace, dev in [(CLIENT_NS, CLIENT_VETH), (SERVER_NS, SERVER_VETH)]:
command = [ command = netns(args, namespace, [
*sudo_prefix(args),
"ip",
"netns",
"exec",
namespace,
"tc", "tc",
"qdisc", "qdisc",
"del", "del",
"dev", "dev",
dev, dev,
"root", "root",
] ])
run_checked( run_checked(
command, command,
log_dir / "qdisc-cleanup" / f"{namespace}-{dev}", log_dir / "qdisc-cleanup" / f"{namespace}-{dev}",
@@ -698,12 +688,19 @@ def cleanup(args: argparse.Namespace, quiet: bool) -> None:
return return
if args.sudo and shutil.which(args.sudo) is None: if args.sudo and shutil.which(args.sudo) is None:
return return
commands = [ commands = []
[*sudo_prefix(args), "ip", "netns", "exec", CLIENT_NS, "tc", "qdisc", "del", "dev", CLIENT_VETH, "root"], if shutil.which("nsenter") is not None:
[*sudo_prefix(args), "ip", "netns", "exec", SERVER_NS, "tc", "qdisc", "del", "dev", SERVER_VETH, "root"], commands.extend([
netns(args, CLIENT_NS, ["tc", "qdisc", "del", "dev", CLIENT_VETH, "root"]),
netns(args, SERVER_NS, ["tc", "qdisc", "del", "dev", SERVER_VETH, "root"]),
])
commands.extend([
[*sudo_prefix(args), "ip", "netns", "delete", CLIENT_NS], [*sudo_prefix(args), "ip", "netns", "delete", CLIENT_NS],
[*sudo_prefix(args), "ip", "netns", "delete", SERVER_NS], [*sudo_prefix(args), "ip", "netns", "delete", SERVER_NS],
] # A setup failure can leave either end of the pair in the host namespace.
[*sudo_prefix(args), "ip", "link", "delete", CLIENT_VETH],
[*sudo_prefix(args), "ip", "link", "delete", SERVER_VETH],
])
for command in commands: for command in commands:
completed = subprocess.run( completed = subprocess.run(
command, command,