1{ pkgs, lib, ... }:
2
3let
4 baud = 57600;
5 tty = "/dev/ttyACM0";
6 port = "tnc0";
7 socatPort = 1234;
8
9 createAX25Node = nodeId: {
10 boot.kernelModules = [ "ax25" ];
11
12 networking.firewall.allowedTCPPorts = [ socatPort ];
13
14 environment.systemPackages = with pkgs; [
15 libax25
16 ax25-tools
17 ax25-apps
18 socat
19 ];
20
21 services.ax25.axports."${port}" = {
22 inherit baud tty;
23 enable = true;
24 callsign = "NOCALL-${toString nodeId}";
25 description = "mocked tnc";
26 };
27
28 services.ax25.axlisten = {
29 enable = true;
30 };
31
32 # All mocks radios will connect back to socat-broker on node 1 in order to get
33 # all messages that are "broadcasted over the ether"
34 systemd.services.ax25-mock-hardware = {
35 description = "mock AX.25 TNC and Radio";
36 wantedBy = [ "default.target" ];
37 before = [
38 "ax25-kissattach-${port}.service"
39 "axlisten.service"
40 ];
41 after = [ "network.target" ];
42 serviceConfig = {
43 Type = "exec";
44 ExecStart = "${pkgs.socat}/bin/socat -d -d tcp:192.168.1.1:${toString socatPort} pty,link=${tty},b${toString baud},raw";
45 };
46 };
47 };
48in
49{
50 name = "ax25Simple";
51 nodes = {
52 node1 = lib.mkMerge [
53 (createAX25Node 1)
54 # mimicking radios on the same frequency
55 {
56 systemd.services.ax25-mock-ether = {
57 description = "mock radio ether";
58 wantedBy = [ "default.target" ];
59 requires = [ "network.target" ];
60 before = [ "ax25-mock-hardware.service" ];
61 # broken needs access to "ss" or "netstat"
62 path = [ pkgs.iproute2 ];
63 serviceConfig = {
64 Type = "exec";
65 ExecStart = "${pkgs.socat}/bin/socat-broker.sh tcp4-listen:${toString socatPort}";
66 };
67 postStart = "${pkgs.coreutils}/bin/sleep 2";
68 };
69 }
70 ];
71 node2 = createAX25Node 2;
72 node3 = createAX25Node 3;
73 };
74 testScript =
75 { ... }:
76 ''
77 def wait_for_machine(m):
78 m.succeed("lsmod | grep ax25")
79 m.wait_for_unit("ax25-axports.target")
80 m.wait_for_unit("axlisten.service")
81 m.fail("journalctl -o cat -u axlisten.service | grep -i \"no AX.25 port data configured\"")
82
83 # start the first node since the socat-broker needs to be running
84 node1.start()
85 node1.wait_for_unit("ax25-mock-ether.service")
86 wait_for_machine(node1)
87
88 node2.start()
89 node3.start()
90 wait_for_machine(node2)
91 wait_for_machine(node3)
92
93 # Node 1 -> Node 2
94 node1.succeed("echo hello | ax25_call ${port} NOCALL-1 NOCALL-2")
95 node2.sleep(1)
96 node2.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-1 to NOCALL-2 ctl I00\" | grep hello")
97
98 # Node 1 -> Node 3
99 node1.succeed("echo hello | ax25_call ${port} NOCALL-1 NOCALL-3")
100 node3.sleep(1)
101 node3.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-1 to NOCALL-3 ctl I00\" | grep hello")
102
103 # Node 2 -> Node 1
104 # must sleep due to previous ax25_call lingering
105 node2.sleep(5)
106 node2.succeed("echo hello | ax25_call ${port} NOCALL-2 NOCALL-1")
107 node1.sleep(1)
108 node1.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-2 to NOCALL-1 ctl I00\" | grep hello")
109
110 # Node 2 -> Node 3
111 node2.succeed("echo hello | ax25_call ${port} NOCALL-2 NOCALL-3")
112 node3.sleep(1)
113 node3.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-2 to NOCALL-3 ctl I00\" | grep hello")
114
115 # Node 3 -> Node 1
116 # must sleep due to previous ax25_call lingering
117 node3.sleep(5)
118 node3.succeed("echo hello | ax25_call ${port} NOCALL-3 NOCALL-1")
119 node1.sleep(1)
120 node1.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-3 to NOCALL-1 ctl I00\" | grep hello")
121
122 # Node 3 -> Node 2
123 node3.succeed("echo hello | ax25_call ${port} NOCALL-3 NOCALL-2")
124 node2.sleep(1)
125 node2.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-3 to NOCALL-2 ctl I00\" | grep hello")
126 '';
127}