Unverified Commit 706b0a02 authored by Matthew Croughan's avatar Matthew Croughan Committed by GitHub
Browse files

nixos/ax25/{axports,axlisten}: init (#399020)

parents 024bdbed ad963072
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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
+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}";
      };
    };
  };
}
+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;
  };
}
+1 −0
Original line number Diff line number Diff line
@@ -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;

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")
      '';
  }
)