More network test work

Signed-off-by: magic_rb <magic_rb@redalder.org>
This commit is contained in:
magic_rb 2024-08-14 15:59:24 +02:00
parent e0cc1a7efe
commit 0e7389fcbd
No known key found for this signature in database
GPG key ID: 08D5287CC5DDCA0E
13 changed files with 1183 additions and 45 deletions

View file

@ -1,38 +1,5 @@
{self, ...}: {
perSystem = {pkgs, ...}: {
checks.router = pkgs.stdenv.mkDerivation {
name = "router";
phases = [
"runPhase"
];
env.ROUTER_CMD =
(self.nixosConfigurations.liveusb.extendModules {
modules = [
"${pkgs.path}/nixos/modules/testing/test-instrumentation.nix"
];
})
.config
.system
.build
.vm;
nativeBuildInputs = with pkgs; [
iproute2
(python3.withPackages (ps:
with ps; [
ptpython
colorama
junit-xml
]))
];
runPhase = ''
PYTHONPATH=${pkgs.path}/nixos/lib/test-driver python ${./router.py}
mkdir -p $out
touch $out/success
'';
};
};
{...}: {
imports = [
./hel
];
}

View file

@ -0,0 +1,26 @@
From ce950ae409a3a7d9ba259e61e0f029005a12512e Mon Sep 17 00:00:00 2001
From: magic_rb <richard@brezak.sk>
Date: Tue, 6 Aug 2024 19:04:43 +0200
Subject: [PATCH 1/3] Don't close stdout, stderr when running a script
Signed-off-by: magic_rb <richard@brezak.sk>
---
pppd/main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pppd/main.c b/pppd/main.c
index 8310c98..de6d6f4 100644
--- a/pppd/main.c
+++ b/pppd/main.c
@@ -1918,7 +1918,7 @@ run_program(char *prog, char * const *args, int must_exist, void (*done)(void *)
return 0;
}
- pid = ppp_safe_fork(fd_devnull, fd_devnull, fd_devnull);
+ pid = ppp_safe_fork(fd_devnull, 1, 2);
if (pid == -1) {
error("Failed to create child process for %s: %m", prog);
return -1;
--
2.44.1

View file

@ -0,0 +1,46 @@
From 761f4fdc1165da91d3b758caa853f8a9f6ff61a2 Mon Sep 17 00:00:00 2001
From: magic_rb <richard@brezak.sk>
Date: Tue, 6 Aug 2024 19:05:09 +0200
Subject: [PATCH 2/3] Call scripts with the same environ as pppd
Signed-off-by: magic_rb <richard@brezak.sk>
---
pppd/main.c | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/pppd/main.c b/pppd/main.c
index de6d6f4..defb002 100644
--- a/pppd/main.c
+++ b/pppd/main.c
@@ -125,6 +125,8 @@
#include "atcp.h"
#endif
+extern char** environ;
+
/* interface vars */
char ifname[IFNAMSIZ]; /* Interface name */
int ifunit; /* Interface unit number */
@@ -1962,7 +1964,18 @@ run_program(char *prog, char * const *args, int must_exist, void (*done)(void *)
/* run the program */
update_script_environment();
- execve(prog, args, script_env);
+
+ /* combine environs */
+ size_t stock_environ_length = strlen(environ);
+ size_t script_environ_length = strlen(script_env);
+ char** combined_environ = calloc(stock_environ_length + script_environ_length + 1, sizeof(*combined_environ));
+ memcpy(combined_environ, script_env, script_environ_length);
+ memcpy(combined_environ + script_environ_length, environ, stock_environ_length);
+
+ printf("invoking with %ul env vars\n", combined_environ);
+
+ execve(prog, args, environ);
+ free(combined_environ);
if (must_exist || errno != ENOENT) {
/* have to reopen the log, there's nowhere else
for the message to go. */
--
2.44.1

View file

@ -0,0 +1,35 @@
From bb890feb75093513194058a5fb2d7728d5a07283 Mon Sep 17 00:00:00 2001
From: magic_rb <richard@brezak.sk>
Date: Fri, 9 Aug 2024 14:33:50 +0200
Subject: [PATCH 3/3] Wait for ip up/down scripts
Signed-off-by: magic_rb <richard@brezak.sk>
---
pppd/ipcp.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pppd/ipcp.c b/pppd/ipcp.c
index 5d9ff11..49f1e9a 100644
--- a/pppd/ipcp.c
+++ b/pppd/ipcp.c
@@ -1996,7 +1996,7 @@ ipcp_up(fsm *f)
*/
if (ipcp_script_state == s_down && ipcp_script_pid == 0) {
ipcp_script_state = s_up;
- ipcp_script(path_ipup, 0);
+ ipcp_script(path_ipup, 1);
}
}
@@ -2045,7 +2045,7 @@ ipcp_down(fsm *f)
/* Execute the ip-down script */
if (ipcp_script_state == s_up && ipcp_script_pid == 0) {
ipcp_script_state = s_down;
- ipcp_script(path_ipdown, 0);
+ ipcp_script(path_ipdown, 1);
}
}
--
2.44.1

View file

@ -0,0 +1,51 @@
{
inputs',
pkgs,
lib,
...
}: let
inherit
(lib)
concatMap
;
in {
imports = [
inputs'.self.nixosModules.ifstate
inputs'.self.nixosModules.netnsIf
./qemu-vm.nix
];
nixpkgs.overlays = [
inputs'.self.overlays.ifstate
];
environment.systemPackages = [pkgs.tcpdump];
services.ifstate = {
enable = true;
settings = {
interfaces = [
{
name = "lan";
link = {
kind = "physical";
permaddr = "00:11:22:33:43:00";
state = "up";
};
}
];
};
};
networking = {
hostName = "client";
useDHCP = false;
interfaces.lan.useDHCP = true;
firewall.enable = false;
};
virtualisation.qemu.options = [
"-net nic,model=e1000,macaddr=00:11:22:33:43:00,netdev=lan"
"-netdev vde,id=lan,sock=../../../hel/switch"
];
}

112
nixos/tests/hel/default.nix Normal file
View file

@ -0,0 +1,112 @@
{
inputs,
lib,
self,
...
}: let
inherit
(lib)
singleton
concatMap
mkDefault
mkImageMediaOverride
;
in {
flake.nixosConfigurations.hela = inputs.nixpkgs-stable.lib.nixosSystem {
system = "x86_64-linux";
modules =
singleton
(import ./hela.nix);
specialArgs = {
inputs' = inputs;
};
};
flake.nixosConfigurations."jörmungandr" = inputs.nixpkgs-stable.lib.nixosSystem {
system = "x86_64-linux";
modules =
singleton
(import ./jormungandr.nix);
specialArgs = {
inputs' = inputs;
};
};
flake.nixosConfigurations.client = inputs.nixpkgs-stable.lib.nixosSystem {
system = "x86_64-linux";
modules =
singleton
(import ./client.nix);
specialArgs = {
inputs' = inputs;
};
};
perSystem = {
lib,
pkgs,
system,
...
}: {
checks.router = pkgs.stdenv.mkDerivation {
name = "router";
phases = [
"runPhase"
];
env = {
HELA_SYSTEM =
(self.nixosConfigurations.hela.extendModules {
modules = [
"${pkgs.path}/nixos/modules/testing/test-instrumentation.nix"
];
})
.config
.system
.build
.vm;
CLIENT_SYSTEM =
(self.nixosConfigurations.client.extendModules {
modules = [
"${pkgs.path}/nixos/modules/testing/test-instrumentation.nix"
];
})
.config
.system
.build
.vm;
JORMUNGANDR_SYSTEM =
(self.nixosConfigurations."jörmungandr".extendModules {
modules = [
"${pkgs.path}/nixos/modules/testing/test-instrumentation.nix"
];
})
.config
.system
.build
.vm;
};
nativeBuildInputs = with pkgs; [
iproute2
vde2
socat
(python3.withPackages (ps:
with ps; [
ptpython
colorama
junit-xml
]))
];
runPhase = ''
PYTHONPATH=${pkgs.path}/nixos/lib/test-driver python ${../router.py}
mkdir -p $out
touch $out/success
'';
};
};
}

302
nixos/tests/hel/hela.nix Normal file
View file

@ -0,0 +1,302 @@
{
pkgs,
config,
inputs',
lib,
...
}: let
inherit
(lib)
mkDefault
concatMap
mkImageMediaOverride
getExe'
;
notnft = inputs'.notnft.lib.${pkgs.stdenv.system};
in {
_module.args = {inherit notnft;};
imports = [
inputs'.self.nixosModules.ifstate
inputs'.self.nixosModules.notnft-ns
./qemu-vm.nix
];
nixpkgs.overlays = [
inputs'.self.overlays.ifstate
];
networking = {
hostName = "hela";
useDHCP = false;
firewall.enable = false;
};
environment.systemPackages = [pkgs.tcpdump];
networking.notnft.enable = true;
networking.notnft.namespaces.default.firewall.interfaces."lan".rules =
# ---
with notnft.dsl;
with payload;
# ---
{
input = [
[(is.eq ip.protocol (f: f.icmp)) accept]
[(is.eq ip.protocol (f: f.udp)) (is.eq th.dport 67) accept]
];
};
networking.notnft.namespaces.default.rules =
# ---
with notnft.dsl;
with payload;
# ---
ruleset {
filter = add table {family = f: f.inet;} {
input =
add chain {
type = f: f.filter;
hook = f: f.input;
prio = -300;
policy = f: f.drop;
}
[(is.eq meta.iifname "lan") (jump "input-lan")];
};
};
networking.notnft.namespaces.dmz.firewall.interfaces."internet".rules =
# ---
with notnft.dsl;
with payload;
# ---
{
input = [
[(is.eq meta.protocol (_: 34915)) accept] # allow PPPoE
[drop]
];
};
networking.notnft.namespaces.dmz.rules =
# ---
with notnft.dsl;
with payload;
# ---
ruleset {
filter = add table {family = f: f.inet;} {
input =
add chain {
type = f: f.filter;
hook = f: f.input;
prio = -300;
policy = f: f.drop;
}
# accept related, established and drop invalid
[
(vmap ct.state {
established = accept;
related = accept;
invalid = drop;
})
]
[(is.eq meta.iifname "internet") (jump "input-internet")];
};
};
services.ifstate = {
enable = true;
settings = {
namespaces = {
dmz = {
interfaces = [
{
name = "wan";
link = {
kind = "physical";
permaddr = "00:11:22:33:44:10";
state = "up";
};
}
{
name = "internet";
link = {
kind = "vlan";
link = "wan";
vlan_id = 6;
state = "up";
};
}
{
name = "iptv";
link = {
kind = "vlan";
link = "wan";
vlan_id = 4;
};
}
{
name = "telephony";
link = {
kind = "vlan";
link = "wan";
vlan_id = 7;
};
}
];
};
};
interfaces = [
{
name = "eth1";
link = {
kind = "physical";
address = "00:11:22:33:44:01";
state = "down";
};
}
{
name = "eth2";
link = {
kind = "physical";
address = "00:11:22:33:44:02";
state = "down";
};
}
{
name = "lan0";
link = {
kind = "physical";
address = "00:11:22:33:44:11";
master = "lan";
state = "up";
};
}
{
name = "lan1";
link = {
kind = "physical";
address = "00:11:22:33:44:12";
master = "lan";
state = "up";
};
}
{
name = "lan2";
link = {
kind = "physical";
address = "00:11:22:33:44:13";
master = "lan";
state = "up";
};
}
{
name = "village";
link = {
kind = "vlan";
link = "lan";
vlan_id = 1;
};
}
{
name = "bastion";
link = {
kind = "vlan";
link = "lan";
vlan_id = 2;
};
}
{
name = "lan";
link = {
kind = "bridge";
state = "up";
};
addresses = [
"10.10.0.1/24"
];
}
];
};
};
services.kea.dhcp4 = {
enable = true;
settings = {
interfaces-config.interfaces = [
"lan"
];
subnet4 = [
{
pools = [
{pool = "10.10.0.2 - 10.10.0.200";}
];
id = 1;
subnet = "10.10.0.0/24";
option-data = [
{
name = "routers";
data = "10.10.0.1";
}
];
}
];
};
};
# reference: https://gist.github.com/c0deaddict/64cc4ee262e428e8d8ff4fe507ab1f9b
services.pppd = let
rp-pppoe = pkgs.callPackage ./rp-pppoe.nix {};
in {
enable = true;
peers.kpn = {
config = ''
plugin ${rp-pppoe}/etc/ppp/plugins/rp-pppoe.so
nic-internet
name "kpn"
noauth
hide-password
debug
+ipv6
ipv6cp-accept-local
noipdefault
defaultroute
defaultroute6
persist
maxfail 0
holdoff 5
mtu 1500
mru 1500
'';
};
};
systemd.services.pppd-kpn = {
after = ["ifstate.service"];
serviceConfig.NetworkNamespacePath = "/var/run/netns/dmz";
};
virtualisation.qemu.options =
(concatMap (index: let
index' = toString index;
in [
"-net nic,model=e1000,macaddr=00:11:22:33:44:0${index'},netdev=eth${index'}"
"-netdev stream,id=eth${index'},addr.type=unix,addr.path=/dev/null"
# "-netdev vde,id=eth${index'},sock=../../../hel/switch"
]) [1 2])
++ [
"-net nic,model=e1000,macaddr=00:11:22:33:44:10,netdev=wan"
"-netdev stream,id=wan,server=on,addr.type=unix,addr.path=../../../midgard/link"
# "-netdev vde,id=wan,sock=../../../midgard/switch"
"-net nic,model=e1000,macaddr=00:11:22:33:44:11,netdev=lan1"
"-netdev vde,id=lan1,sock=../../../hel/switch"
]
++ (concatMap (index: let
index' = toString index;
in [
"-net nic,model=e1000,macaddr=00:11:22:33:44:1${index'},netdev=lan${index'}"
"-netdev stream,id=lan${index'},addr.type=unix,addr.path=/dev/null"
# "-netdev vde,id=lan${index'},sock=../../../hel/switch"
]) [2 3]);
}

View file

@ -0,0 +1,92 @@
{
inputs',
pkgs,
lib,
...
}: let
inherit
(lib)
concatMap
getExe'
;
in {
imports = [
inputs'.self.nixosModules.ifstate
inputs'.self.nixosModules.netnsIf
./pppoe-server.nix
./qemu-vm.nix
];
nixpkgs.overlays = [
inputs'.self.overlays.ifstate
(final: _: {
rp-pppoe = final.callPackage ./rp-pppoe.nix {
ppp = final.ppp.overrideAttrs (_: {
patches = [
./0003-Wait-for-ip-up-down-scripts.patch
./0002-Call-scripts-with-the-same-environ-as-pppd.patch
];
});
};
})
];
environment.systemPackages = [
pkgs.tcpdump
];
services.ifstate = {
enable = true;
settings = {
interfaces = [
{
name = "wan";
link = {
kind = "physical";
permaddr = "00:11:22:33:42:00";
state = "up";
};
}
{
name = "internet";
link = {
kind = "vlan";
link = "wan";
vlan_id = 6;
state = "up";
};
}
{
name = "www-br";
link = {
kind = "bridge";
state = "up";
};
}
];
};
};
services.pppoe-server.kpn = {
interface = "internet";
localAddress = "10.0.0.1";
remoteAddressFile = pkgs.writeText "kpn-remote-address-file" ''
10.67.15.1
'';
# ifUpScript = pkgs.writeShellScript "ppd-if-up" ''
# ${getExe' pkgs.iproute2 "ip"} link set dev "$PPP_IFACE" master ppp-br
# '';
};
networking = {
hostName = "jormungandr";
useDHCP = false;
firewall.enable = false;
};
virtualisation.qemu.options = [
"-net nic,model=e1000,macaddr=00:11:22:33:42:00,netdev=wan"
"-netdev stream,id=wan,addr.type=unix,addr.path=../../../midgard/link"
# "-netdev vde,id=wan,sock=../../../midgard/switch"
];
}

View file

@ -0,0 +1,204 @@
{
config,
pkgs,
lib,
options,
...
}: let
inherit
(lib)
mkOption
types
flip
mapAttrs'
nameValuePair
concatStringsSep
concatMapStringsSep
pipe
optional
mapAttrsToList
filterAttrs
elem
mkPackageOption
attrNames
singleton
optionalString
;
cfg = config.services.pppoe-server;
in {
options.services.pppoe-server = mkOption {
default = {};
type = types.attrsOf (types.submodule ({
name,
config,
...
}: {
freeformType = with types;
oneOf [int str];
options = {
package =
mkPackageOption pkgs "rp-pppoe" {};
interface = mkOption {
type = types.str;
description = ''
Which interface should `pppoe-server' bind to, this interface must
be an L2 interface and pass PPPoE packets through unchanged.
`pppoe-server' initially broadcasts a PADI packet.
'';
};
localAddress = mkOption {
type = types.str;
description = ''
Which address should be assigned to the local PPP interface. Only
one such IP address per daemon is possible.
'';
};
startingRemoteAddress = mkOption {
type = types.nullOr types.str;
description = ''
Which starting address to utilize, `pppoe-server' will keep track
of assigned addresses and automatically reclaim them as clients
disconnect.
'';
default = null;
};
maxNumberOfConnections = mkOption {
type = types.int;
description = ''
The maximum number of concurrent connections to allow, also limits
the number of IP addresses and therefore the range, as a side
effect.
'';
default = 64;
};
remoteAddressFile = mkOption {
type = types.nullOr types.path;
description = ''
Reads the specified file fname which is a text file
consisting of one IP address per line. These IP addresses
will be assigned to clients. The number of sessions allowed
will equal the number of addresses found in the file.
The `remoteAddressFile' option overrides both
`startingRemoteAddress' and `maxNumberOfConnections'.
In addition to containing IP addresses, the pool file can
contain lines of the form:
- `a.b.c.d-e` which includes all IP addresses from`a.b.c.d` to
`a.b.c.e`.
For example, the line:
- `1.2.3.4-7` is equivalent to:
```
1.2.3.4
1.2.3.5
1.2.3.6
1.2.3.7
```
'';
default = null;
};
ifUpScript = mkOption {
type = with types; nullOr path;
description = ''
Script to run, when the `ppp` interface goes up.
'';
default = null;
};
ifDownScript = mkOption {
type = with types; nullOr path;
description = ''
Script to run, when the `ppp` interface goes down.
'';
default = null;
};
pppdSettings = mkOption {
type = with types; attrsOf (listOf (oneOf [str int path]));
default = {};
description = ''
Settings passed to PPPD after it is started by `pppoe-server`.
'';
};
};
config = {
pppdSettings = {
ip-up-script = singleton (pkgs.writeShellScript "ip-up-${name}" ''
{
echo "Signalling ready to systemd"
systemd-notify --ready
${optionalString (config.ifUpScript != null) config.ifUpScript}
} | logger -t pppd-ip-up
'');
ip-down-script = singleton (pkgs.writeShellScript "ip-down-${name}" ''
{
echo "Signalling stopping to systemd"
systemd-notify --stopping
${optionalString (config.ifDownScript != null) config.ifDownScript}
} | logger -t pppd-ip-down
'');
};
};
}));
};
config.systemd.services = flip mapAttrs' cfg (
n: v: let
pppdSettingsFile =
pkgs.writeText "pppd-${n}.conf"
((pipe v.pppdSettings [
(mapAttrsToList (n: v: n + " " + concatMapStringsSep " " toString v))
(concatStringsSep "\n")
])
+ "\n");
in
nameValuePair
"pppoe-server-${n}"
{
before = ["network.target"];
wants = ["network.target"];
after = ["network-pre.target" "ifstate.service"];
wantedBy = ["multi-user.target"];
path = [
pkgs.util-linux
];
serviceConfig = {
Type = "notify";
NotifyAccess = "all";
ExecStart =
"${v.package}/bin/pppoe-server "
+ concatStringsSep " " (
[
"-F"
"-k"
"-I ${v.interface}"
"-L ${v.localAddress}"
"-g ${v.package}/etc/ppp/plugins/rp-pppoe.so"
"-O ${pppdSettingsFile}"
]
++ optional (v.startingRemoteAddress != null)
"-R ${v.startingRemoteAddress}"
++ optional (v.maxNumberOfConnections != null)
"-N ${toString v.maxNumberOfConnections}"
++ optional (v.remoteAddressFile != null)
"-p ${v.remoteAddressFile}"
++ (flip mapAttrsToList
(filterAttrs (
n: _:
!(elem n (attrNames (options.services.pppoe-server.type.getSubOptions [])))
)
v)
(n: v: "-${n} ${toString v}"))
);
};
}
);
}

View file

@ -0,0 +1,29 @@
{
inputs',
lib,
...
}: let
inherit
(lib)
mkDefault
mkImageMediaOverride
;
in {
imports = [
"${inputs'.nixpkgs-stable}/nixos/modules/virtualisation/qemu-vm.nix"
];
services.getty.autologinUser = "nixos";
users.users.nixos = {
isNormalUser = true;
extraGroups = ["wheel" "video"];
# Allow the graphical user to login without password
initialHashedPassword = "";
};
# Allow the user to log in as root without a password.
users.users.root.initialHashedPassword = "";
# Allow passwordless sudo from nixos user
security.sudo = {
enable = mkDefault true;
wheelNeedsPassword = mkImageMediaOverride false;
};
}

View file

@ -0,0 +1,29 @@
{
stdenv,
fetchFromGitHub,
ppp,
lib,
}:
stdenv.mkDerivation {
name = "rp-pppoe";
version = "4.0.0-next";
src = fetchFromGitHub {
owner = "dfskoll";
repo = "rp-pppoe";
rev = "5748840ac83e7e54fecb1b0034407379c2d6c324";
hash = "sha256-3QXtVu3IfOoJD60h44cqYGPix/2Gl0IKyFTyDwZIc/0=";
};
buildInputs = [ppp];
sourceRoot = "source/src";
env.CPATH = lib.concatStringsSep ":" ["${ppp}/include"];
makeFlags = [
"DESTDIR=$(out)"
];
configureFlags = [
"--enable-plugin"
"--prefix=/"
];
postInstall = ''
test -f $out/etc/ppp/plugins/rp-pppoe.so
'';
}

92
nixos/tests/hel/shq Normal file
View file

@ -0,0 +1,92 @@
{
inputs',
pkgs,
lib,
...
}: let
inherit
(lib)
concatMap
getExe'
;
in {
imports = [
inputs'.self.nixosModules.ifstate
inputs'.self.nixosModules.netnsIf
./pppoe-server.nix
./qemu-vm.nix
];
nixpkgs.overlays = [
inputs'.self.overlays.ifstate
(final: _: {
rp-pppoe = final.callPackage ./rp-pppoe.nix {
ppp = final.ppp.overrideAttrs (_: {
patches = [
./0003-Wait-for-ip-up-down-scripts.patch
./0002-Call-scripts-with-the-same-environ-as-pppd.patch
];
});
};
})
];
environment.systemPackages = [
pkgs.tcpdump
];
services.ifstate = {
enable = true;
settings = {
interfaces = [
{
name = "wan";
link = {
kind = "physical";
permaddr = "00:11:22:33:42:00";
state = "up";
};
}
{
name = "internet";
link = {
kind = "vlan";
link = "wan";
vlan_id = 6;
state = "up";
};
}
{
name = "www-br";
link = {
kind = "bridge";
state = "up";
};
}
];
};
};
services.pppoe-server.kpn = {
interface = "internet";
localAddress = "10.0.0.1";
remoteAddressFile = pkgs.writeText "kpn-remote-address-file" ''
10.67.15.1
'';
# ifUpScript = pkgs.writeShellScript "ppd-if-up" ''
# ${getExe' pkgs.iproute2 "ip"} link set dev "$PPP_IFACE" master ppp-br
# '';
};
networking = {
hostName = "jormungandr";
useDHCP = false;
firewall.enable = false;
};
virtualisation.qemu.options = [
"-net nic,model=e1000,macaddr=00:11:22:33:42:00,netdev=wan"
"-netdev stream,id=wan,addr.type=unix,addr.path=../../../midgard/link"
# "-netdev vde,id=wan,sock=../../../midgard/switch"
];
}

View file

@ -1,21 +1,174 @@
from test_driver.machine import Machine, StartCommand, NixStartScript
from test_driver.logger import TerminalLogger
from pathlib import Path
from typing import Any
import os
import sys
import socket
import subprocess
import pty
import time
hela_system = os.getenv("HELA_SYSTEM")
client_system = os.getenv("CLIENT_SYSTEM")
jormungandr_system = os.getenv("JORMUNGANDR_SYSTEM")
interactive = os.getenv("ROUTER_INTERACTIVE")
def make_machine(out_dir: Path, tmp_dir: Path) -> Machine:
class FileLogger(TerminalLogger):
@staticmethod
def _eprint(*args: object, **kwargs: Any) -> None:
return
class NixStartScriptSetsid(NixStartScript):
def run(
self,
state_dir: Path,
shared_dir: Path,
monitor_socket_path: Path,
qmp_socket_path: Path,
shell_socket_path: Path,
allow_reboot: bool,
) -> subprocess.Popen:
return subprocess.Popen(
self.cmd(
monitor_socket_path, qmp_socket_path, shell_socket_path, allow_reboot
),
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
shell=True,
cwd=state_dir,
env=self.build_environment(state_dir, shared_dir),
)
def make_machine(*, system: Path, name: str, out_dir: Path | None = None, tmp_dir: Path | None = None) -> Machine:
if out_dir is None:
out_dir = Path(f"{name}/out")
if tmp_dir is None:
tmp_dir = Path(f"{name}/tmp")
out_dir.mkdir(parents=True, exist_ok=True)
tmp_dir.mkdir(parents=True, exist_ok=True)
return Machine(
out_dir = out_dir.absolute(),
tmp_dir = tmp_dir.absolute(),
start_command = NixStartScript(os.getenv("ROUTER_CMD") + "/bin/run-nixos-vm"),
logger = TerminalLogger(),
name = "router",
start_command = NixStartScript(system + f"/bin/run-{name}-vm") if not interactive else NixStartScriptSetsid(system + f"/bin/run-{name}-vm"),
logger = TerminalLogger() if not interactive else FileLogger(),
name = name,
keep_vm_state = True,
)
router = make_machine(Path("router/out"), Path("router/tmp"))
router.start()
router.wait_for_unit("multi-user.target")
router.shutdown()
def vde_switch(control_socket: Path) -> subprocess.Popen:
control_socket.mkdir(parents = True, exist_ok = True)
pty_master, pty_slave = pty.openpty()
return subprocess.Popen(
["vde_switch", "-unix", str(control_socket.absolute()), "--dirmode", "0700"],
stdin=pty_slave,
)
def vde_plug_unix(path: Path, control_socket: Path, port: int | None = None) -> subprocess.Popen:
port = f"-p {port}" if port is not None else ""
return subprocess.Popen(
["socat", f"UNIX-LISTEN:{path.absolute()}", f"EXEC:vde_plug {port} {control_socket.absolute()}"],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT,
)
out_dir = Path(os.getenv("out"))
def dump_to_out(path: str | Path, content: bytes):
global out_dir
if not isinstance(path, Path):
path = Path(path)
final_path = out_dir / path
final_path.parent.mkdir(parents = True, exist_ok = True)
fh = open(final_path, "w")
fh.write(content)
fh.close()
vde_hel_control_path = Path("hel/switch")
vde_hel = vde_switch(vde_hel_control_path)
vde_midgard_control_path = Path("midgard/switch")
vde_midgard = vde_switch(vde_midgard_control_path)
# kpn_link = subprocess.Popen(
# [
# "socat",
# f"UNIX-LISTEN:{Path('midgard/link.jormungandr').absolute()}",
# f"UNIX-LISTEN:{Path('midgard/link.hela').absolute()}"
# ]
# )
time.sleep(0.5)
hela = make_machine(system = hela_system, name = "hela")
hela.start()
time.sleep(0.5)
client = make_machine(system = client_system, name = "client")
client.start()
jormungandr = make_machine(system = jormungandr_system, name = "jormungandr")
jormungandr.start()
def finalize(exit_code: int) -> None:
global hela, client, jormungandr, vde_hel, vde_midgard
# subprocess.run(["cp", "hela/tmp/vm-state-hela/xchg/tcpdump-dmz-log:0.log", out_dir / "hela-tcpdump-dmz-log:0.log"], check=True)
# subprocess.run(["cp", "hela/tmp/vm-state-hela/xchg/tcpdump-dmz-log:1.log", out_dir / "hela-tcpdump-dmz-log:1.log"], check=True)
# subprocess.run(["cp", "jormungandr/tmp/vm-state-jormungandr/xchg/tcpdump-dmz-log:0.log", out_dir / "jormungandr-tcpdump-dmz-log:0.log"], check=True)
# subprocess.run(["cp", "jormungandr/tmp/vm-state-jormungandr/xchg/tcpdump-dmz-log:1.log", out_dir / "jormungandr-tcpdump-dmz-log:1.log"], check=True)
hela.shutdown()
client.shutdown()
jormungandr.shutdown()
vde_hel.terminate()
vde_midgard.terminate()
vde_hel.wait()
vde_midgard.wait()
sys.exit(exit_code)
try:
if interactive:
import ptpython
ptpython.repl.embed(globals(), locals())
else:
hela.wait_for_unit("multi-user.target")
client.wait_for_unit("multi-user.target")
jormungandr.wait_for_unit("multi-user.target")
# hela.wait_until_succeeds("ip netns exec dmz bash -c $'[ $(ip -f inet addr show ppp0 | sed -En -e \\'s/.*inet ([0-9.]+).*/\\1/p\\' | wc -l) -gt 0 ]'")
dump_to_out("client-default-route", client.succeed("ip route"))
dump_to_out("hela-default-route", hela.succeed("ip route"))
dump_to_out("hela-dmz-route", hela.succeed("ip netns exec dmz ip route"))
dump_to_out("client-default-if", client.succeed("ip addr"))
dump_to_out("hela-default-if", hela.succeed("ip addr"))
dump_to_out("hela-dmz-if", hela.succeed("ip netns exec dmz ip addr"))
client.succeed("ping -c 1 10.10.0.1")
hela.succeed("ping -c 1 10.10.0.2")
hela.succeed("ip netns exec dmz ping -c 1 10.0.0.1")
jormungandr.fail("ping -W 1 -c 1 10.67.15.1")
# client.succeed("ping -c 1 10.0.0.1")
# hela.succeed("systemd-run -u tcpdump-dmz-wan -p StandardOutput=append:/tmp/xchg/tcpdump-dmz-log:0.log -p StandardError=append:/tmp/xchg/tcpdump-dmz-log:1.log ip netns exec dmz tcpdump -i internet")
# jormungandr.succeed("systemd-run -u tcpdump-dmz-wan -p StandardOutput=append:/tmp/xchg/tcpdump-dmz-log:0.log -p StandardError=append:/tmp/xchg/tcpdump-dmz-log:1.log tcpdump -i internet")
except Exception as err:
print(err)
finalize(1)
finalize(0)