Loading nixos/modules/module-list.nix +2 −0 Original line number Diff line number Diff line Loading @@ -1055,6 +1055,8 @@ ./services/networking/atticd.nix ./services/networking/autossh.nix ./services/networking/avahi-daemon.nix ./services/networking/ax25/axlisten.nix ./services/networking/ax25/axports.nix ./services/networking/babeld.nix ./services/networking/bee.nix ./services/networking/biboumi.nix Loading nixos/modules/services/networking/ax25/axlisten.nix 0 → 100644 +62 −0 Original line number Diff line number Diff line { config, lib, pkgs, ... }: let inherit (lib) types ; inherit (lib.modules) mkIf ; inherit (lib.options) mkEnableOption mkOption literalExpression ; cfg = config.services.ax25.axlisten; in { options = { services.ax25.axlisten = { enable = mkEnableOption "AX.25 axlisten daemon"; package = mkOption { type = types.package; default = pkgs.ax25-apps; defaultText = literalExpression "pkgs.ax25-apps"; description = "The ax25-apps package to use."; }; config = mkOption { type = types.str; default = "-art"; description = '' Options that will be passed to the axlisten daemon. ''; }; }; }; config = mkIf cfg.enable { systemd.services.axlisten = { description = "AX.25 traffic monitor"; wantedBy = [ "multi-user.target" ]; after = [ "ax25-axports.target" ]; requires = [ "ax25-axports.target" ]; serviceConfig = { Type = "exec"; ExecStart = "${cfg.package}/bin/axlisten ${cfg.config}"; }; }; }; } nixos/modules/services/networking/ax25/axports.nix 0 → 100644 +149 −0 Original line number Diff line number Diff line { config, lib, pkgs, ... }: let inherit (lib) types ; inherit (lib.strings) concatStringsSep optionalString ; inherit (lib.attrsets) filterAttrs mapAttrsToList mapAttrs' ; inherit (lib.modules) mkIf ; inherit (lib.options) mkEnableOption mkOption mkPackageOption ; cfg = config.services.ax25.axports; enabledAxports = filterAttrs (ax25Name: cfg: cfg.enable) cfg; axportsOpts = { options = { enable = mkEnableOption "Enables the axport interface"; package = mkPackageOption pkgs "ax25-tools" { }; tty = mkOption { type = types.str; example = "/dev/ttyACM0"; description = '' Location of hardware kiss tnc for this interface. ''; }; callsign = mkOption { type = types.str; example = "WB6WLV-7"; description = '' The callsign of the physical interface to bind to. ''; }; description = mkOption { type = types.str; # This cannot be empty since some ax25 tools cant parse /etc/ax25/axports without it default = "NixOS managed tnc"; description = '' Free format description of this interface. ''; }; baud = mkOption { type = types.int; example = 57600; description = '' The serial port speed of this interface. ''; }; paclen = mkOption { type = types.int; default = 255; description = '' Default maximum packet size for this interface. ''; }; window = mkOption { type = types.int; default = 7; description = '' Default window size for this interface. ''; }; kissParams = mkOption { type = types.nullOr types.str; default = null; example = "-t 300 -l 10 -s 12 -r 80 -f n"; description = '' Kissattach parameters for this interface. ''; }; }; }; in { options = { services.ax25.axports = mkOption { type = types.attrsOf (types.submodule axportsOpts); default = { }; description = "Specification of one or more AX.25 ports."; }; }; config = mkIf (enabledAxports != { }) { environment.etc."ax25/axports" = { text = concatStringsSep "\n" ( mapAttrsToList ( portName: portCfg: "${portName} ${portCfg.callsign} ${toString portCfg.baud} ${toString portCfg.paclen} ${toString portCfg.window} ${portCfg.description}" ) enabledAxports ); mode = "0644"; }; systemd.targets.ax25-axports = { description = "AX.25 axports group target"; }; systemd.services = mapAttrs' (portName: portCfg: { name = "ax25-kissattach-${portName}"; value = { description = "AX.25 KISS attached interface for ${portName}"; wantedBy = [ "multi-user.target" ]; before = [ "ax25-axports.target" ]; partOf = [ "ax25-axports.target" ]; serviceConfig = { Type = "exec"; ExecStart = "${portCfg.package}/bin/kissattach ${portCfg.tty} ${portName}"; }; postStart = optionalString (portCfg.kissParams != null) '' ${portCfg.package}/bin/kissparms -p ${portName} ${portCfg.kissParams} ''; }; }) enabledAxports; }; } nixos/tests/all-tests.nix +1 −0 Original line number Diff line number Diff line Loading @@ -213,6 +213,7 @@ in atop = import ./atop.nix { inherit pkgs runTest; }; atticd = runTest ./atticd.nix; atuin = runTest ./atuin.nix; ax25 = handleTest ./ax25.nix { }; audiobookshelf = runTest ./audiobookshelf.nix; auth-mysql = runTest ./auth-mysql.nix; authelia = runTest ./authelia.nix; Loading nixos/tests/ax25.nix 0 → 100644 +131 −0 Original line number Diff line number Diff line import ./make-test-python.nix ( { pkgs, lib, ... }: let baud = 57600; tty = "/dev/ttyACM0"; port = "tnc0"; socatPort = 1234; createAX25Node = nodeId: { boot.kernelPackages = pkgs.linuxPackages_ham; boot.kernelModules = [ "ax25" ]; networking.firewall.allowedTCPPorts = [ socatPort ]; environment.systemPackages = with pkgs; [ libax25 ax25-tools ax25-apps socat ]; services.ax25.axports."${port}" = { inherit baud tty; enable = true; callsign = "NOCALL-${toString nodeId}"; description = "mocked tnc"; }; services.ax25.axlisten = { enable = true; }; # All mocks radios will connect back to socat-broker on node 1 in order to get # all messages that are "broadcasted over the ether" systemd.services.ax25-mock-hardware = { description = "mock AX.25 TNC and Radio"; wantedBy = [ "default.target" ]; before = [ "ax25-kissattach-${port}.service" "axlisten.service" ]; after = [ "network.target" ]; serviceConfig = { Type = "exec"; ExecStart = "${pkgs.socat}/bin/socat -d -d tcp:192.168.1.1:${toString socatPort} pty,link=${tty},b${toString baud},raw"; }; }; }; in { name = "ax25Simple"; nodes = { node1 = lib.mkMerge [ (createAX25Node 1) # mimicking radios on the same frequency { systemd.services.ax25-mock-ether = { description = "mock radio ether"; wantedBy = [ "default.target" ]; requires = [ "network.target" ]; before = [ "ax25-mock-hardware.service" ]; # broken needs access to "ss" or "netstat" path = [ pkgs.iproute2 ]; serviceConfig = { Type = "exec"; ExecStart = "${pkgs.socat}/bin/socat-broker.sh tcp4-listen:${toString socatPort}"; }; postStart = "${pkgs.coreutils}/bin/sleep 2"; }; } ]; node2 = createAX25Node 2; node3 = createAX25Node 3; }; testScript = { ... }: '' def wait_for_machine(m): m.succeed("lsmod | grep ax25") m.wait_for_unit("ax25-axports.target") m.wait_for_unit("axlisten.service") m.fail("journalctl -o cat -u axlisten.service | grep -i \"no AX.25 port data configured\"") # start the first node since the socat-broker needs to be running node1.start() node1.wait_for_unit("ax25-mock-ether.service") wait_for_machine(node1) node2.start() node3.start() wait_for_machine(node2) wait_for_machine(node3) # Node 1 -> Node 2 node1.succeed("echo hello | ax25_call ${port} NOCALL-1 NOCALL-2") node2.sleep(1) node2.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-1 to NOCALL-2 ctl I00\" | grep hello") # Node 1 -> Node 3 node1.succeed("echo hello | ax25_call ${port} NOCALL-1 NOCALL-3") node3.sleep(1) node3.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-1 to NOCALL-3 ctl I00\" | grep hello") # Node 2 -> Node 1 # must sleep due to previous ax25_call lingering node2.sleep(5) node2.succeed("echo hello | ax25_call ${port} NOCALL-2 NOCALL-1") node1.sleep(1) node1.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-2 to NOCALL-1 ctl I00\" | grep hello") # Node 2 -> Node 3 node2.succeed("echo hello | ax25_call ${port} NOCALL-2 NOCALL-3") node3.sleep(1) node3.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-2 to NOCALL-3 ctl I00\" | grep hello") # Node 3 -> Node 1 # must sleep due to previous ax25_call lingering node3.sleep(5) node3.succeed("echo hello | ax25_call ${port} NOCALL-3 NOCALL-1") node1.sleep(1) node1.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-3 to NOCALL-1 ctl I00\" | grep hello") # Node 3 -> Node 2 node3.succeed("echo hello | ax25_call ${port} NOCALL-3 NOCALL-2") node2.sleep(1) node2.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-3 to NOCALL-2 ctl I00\" | grep hello") ''; } ) Loading
nixos/modules/module-list.nix +2 −0 Original line number Diff line number Diff line Loading @@ -1055,6 +1055,8 @@ ./services/networking/atticd.nix ./services/networking/autossh.nix ./services/networking/avahi-daemon.nix ./services/networking/ax25/axlisten.nix ./services/networking/ax25/axports.nix ./services/networking/babeld.nix ./services/networking/bee.nix ./services/networking/biboumi.nix Loading
nixos/modules/services/networking/ax25/axlisten.nix 0 → 100644 +62 −0 Original line number Diff line number Diff line { config, lib, pkgs, ... }: let inherit (lib) types ; inherit (lib.modules) mkIf ; inherit (lib.options) mkEnableOption mkOption literalExpression ; cfg = config.services.ax25.axlisten; in { options = { services.ax25.axlisten = { enable = mkEnableOption "AX.25 axlisten daemon"; package = mkOption { type = types.package; default = pkgs.ax25-apps; defaultText = literalExpression "pkgs.ax25-apps"; description = "The ax25-apps package to use."; }; config = mkOption { type = types.str; default = "-art"; description = '' Options that will be passed to the axlisten daemon. ''; }; }; }; config = mkIf cfg.enable { systemd.services.axlisten = { description = "AX.25 traffic monitor"; wantedBy = [ "multi-user.target" ]; after = [ "ax25-axports.target" ]; requires = [ "ax25-axports.target" ]; serviceConfig = { Type = "exec"; ExecStart = "${cfg.package}/bin/axlisten ${cfg.config}"; }; }; }; }
nixos/modules/services/networking/ax25/axports.nix 0 → 100644 +149 −0 Original line number Diff line number Diff line { config, lib, pkgs, ... }: let inherit (lib) types ; inherit (lib.strings) concatStringsSep optionalString ; inherit (lib.attrsets) filterAttrs mapAttrsToList mapAttrs' ; inherit (lib.modules) mkIf ; inherit (lib.options) mkEnableOption mkOption mkPackageOption ; cfg = config.services.ax25.axports; enabledAxports = filterAttrs (ax25Name: cfg: cfg.enable) cfg; axportsOpts = { options = { enable = mkEnableOption "Enables the axport interface"; package = mkPackageOption pkgs "ax25-tools" { }; tty = mkOption { type = types.str; example = "/dev/ttyACM0"; description = '' Location of hardware kiss tnc for this interface. ''; }; callsign = mkOption { type = types.str; example = "WB6WLV-7"; description = '' The callsign of the physical interface to bind to. ''; }; description = mkOption { type = types.str; # This cannot be empty since some ax25 tools cant parse /etc/ax25/axports without it default = "NixOS managed tnc"; description = '' Free format description of this interface. ''; }; baud = mkOption { type = types.int; example = 57600; description = '' The serial port speed of this interface. ''; }; paclen = mkOption { type = types.int; default = 255; description = '' Default maximum packet size for this interface. ''; }; window = mkOption { type = types.int; default = 7; description = '' Default window size for this interface. ''; }; kissParams = mkOption { type = types.nullOr types.str; default = null; example = "-t 300 -l 10 -s 12 -r 80 -f n"; description = '' Kissattach parameters for this interface. ''; }; }; }; in { options = { services.ax25.axports = mkOption { type = types.attrsOf (types.submodule axportsOpts); default = { }; description = "Specification of one or more AX.25 ports."; }; }; config = mkIf (enabledAxports != { }) { environment.etc."ax25/axports" = { text = concatStringsSep "\n" ( mapAttrsToList ( portName: portCfg: "${portName} ${portCfg.callsign} ${toString portCfg.baud} ${toString portCfg.paclen} ${toString portCfg.window} ${portCfg.description}" ) enabledAxports ); mode = "0644"; }; systemd.targets.ax25-axports = { description = "AX.25 axports group target"; }; systemd.services = mapAttrs' (portName: portCfg: { name = "ax25-kissattach-${portName}"; value = { description = "AX.25 KISS attached interface for ${portName}"; wantedBy = [ "multi-user.target" ]; before = [ "ax25-axports.target" ]; partOf = [ "ax25-axports.target" ]; serviceConfig = { Type = "exec"; ExecStart = "${portCfg.package}/bin/kissattach ${portCfg.tty} ${portName}"; }; postStart = optionalString (portCfg.kissParams != null) '' ${portCfg.package}/bin/kissparms -p ${portName} ${portCfg.kissParams} ''; }; }) enabledAxports; }; }
nixos/tests/all-tests.nix +1 −0 Original line number Diff line number Diff line Loading @@ -213,6 +213,7 @@ in atop = import ./atop.nix { inherit pkgs runTest; }; atticd = runTest ./atticd.nix; atuin = runTest ./atuin.nix; ax25 = handleTest ./ax25.nix { }; audiobookshelf = runTest ./audiobookshelf.nix; auth-mysql = runTest ./auth-mysql.nix; authelia = runTest ./authelia.nix; Loading
nixos/tests/ax25.nix 0 → 100644 +131 −0 Original line number Diff line number Diff line import ./make-test-python.nix ( { pkgs, lib, ... }: let baud = 57600; tty = "/dev/ttyACM0"; port = "tnc0"; socatPort = 1234; createAX25Node = nodeId: { boot.kernelPackages = pkgs.linuxPackages_ham; boot.kernelModules = [ "ax25" ]; networking.firewall.allowedTCPPorts = [ socatPort ]; environment.systemPackages = with pkgs; [ libax25 ax25-tools ax25-apps socat ]; services.ax25.axports."${port}" = { inherit baud tty; enable = true; callsign = "NOCALL-${toString nodeId}"; description = "mocked tnc"; }; services.ax25.axlisten = { enable = true; }; # All mocks radios will connect back to socat-broker on node 1 in order to get # all messages that are "broadcasted over the ether" systemd.services.ax25-mock-hardware = { description = "mock AX.25 TNC and Radio"; wantedBy = [ "default.target" ]; before = [ "ax25-kissattach-${port}.service" "axlisten.service" ]; after = [ "network.target" ]; serviceConfig = { Type = "exec"; ExecStart = "${pkgs.socat}/bin/socat -d -d tcp:192.168.1.1:${toString socatPort} pty,link=${tty},b${toString baud},raw"; }; }; }; in { name = "ax25Simple"; nodes = { node1 = lib.mkMerge [ (createAX25Node 1) # mimicking radios on the same frequency { systemd.services.ax25-mock-ether = { description = "mock radio ether"; wantedBy = [ "default.target" ]; requires = [ "network.target" ]; before = [ "ax25-mock-hardware.service" ]; # broken needs access to "ss" or "netstat" path = [ pkgs.iproute2 ]; serviceConfig = { Type = "exec"; ExecStart = "${pkgs.socat}/bin/socat-broker.sh tcp4-listen:${toString socatPort}"; }; postStart = "${pkgs.coreutils}/bin/sleep 2"; }; } ]; node2 = createAX25Node 2; node3 = createAX25Node 3; }; testScript = { ... }: '' def wait_for_machine(m): m.succeed("lsmod | grep ax25") m.wait_for_unit("ax25-axports.target") m.wait_for_unit("axlisten.service") m.fail("journalctl -o cat -u axlisten.service | grep -i \"no AX.25 port data configured\"") # start the first node since the socat-broker needs to be running node1.start() node1.wait_for_unit("ax25-mock-ether.service") wait_for_machine(node1) node2.start() node3.start() wait_for_machine(node2) wait_for_machine(node3) # Node 1 -> Node 2 node1.succeed("echo hello | ax25_call ${port} NOCALL-1 NOCALL-2") node2.sleep(1) node2.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-1 to NOCALL-2 ctl I00\" | grep hello") # Node 1 -> Node 3 node1.succeed("echo hello | ax25_call ${port} NOCALL-1 NOCALL-3") node3.sleep(1) node3.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-1 to NOCALL-3 ctl I00\" | grep hello") # Node 2 -> Node 1 # must sleep due to previous ax25_call lingering node2.sleep(5) node2.succeed("echo hello | ax25_call ${port} NOCALL-2 NOCALL-1") node1.sleep(1) node1.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-2 to NOCALL-1 ctl I00\" | grep hello") # Node 2 -> Node 3 node2.succeed("echo hello | ax25_call ${port} NOCALL-2 NOCALL-3") node3.sleep(1) node3.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-2 to NOCALL-3 ctl I00\" | grep hello") # Node 3 -> Node 1 # must sleep due to previous ax25_call lingering node3.sleep(5) node3.succeed("echo hello | ax25_call ${port} NOCALL-3 NOCALL-1") node1.sleep(1) node1.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-3 to NOCALL-1 ctl I00\" | grep hello") # Node 3 -> Node 2 node3.succeed("echo hello | ax25_call ${port} NOCALL-3 NOCALL-2") node2.sleep(1) node2.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-3 to NOCALL-2 ctl I00\" | grep hello") ''; } )