STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_daq_interface.cpp
Go to the documentation of this file.
1/**
2 * Which NIC board traffic leaves from.
3 *
4 * The DAQ used to own its machine, so an unbound socket and "the board NIC" were the same thing.
5 * It now shares the apps box with the Docker stack and the site LAN, and resolveDaqBindAddress()
6 * is what re-establishes the guarantee. Every sender in the pipeline routes through it, so its
7 * four outcomes are pinned here rather than left to be discovered on a rig.
8 *
9 * Addresses are chosen so the results do not depend on the machine running the test:
10 * - 127.0.0.x is loopback, present everywhere (this is also the shape the sim uses)
11 * - 192.0.2.x is TEST-NET-1 (RFC 5737), guaranteed not assigned to any interface
12 * - 198.51.100.x is TEST-NET-2, likewise — used for "no NIC on the board subnet"
13 *
14 * The ambiguous case (two interfaces on the board subnet) is deliberately not tested: it cannot
15 * be produced without adding an address to the host, which a unit test has no business doing.
16 */
17#include <sys/socket.h>
18#include <unistd.h>
19
20#include <iostream>
21#include <string>
22
23#include "config/Config.hpp"
24#include "net/DaqInterface.hpp"
25
26static int g_failures = 0;
27
28static void check(bool ok, const std::string& what) {
29 std::cout << (ok ? " ok " : " FAIL ") << what << std::endl;
30 if (!ok)
31 g_failures++;
32}
33
34/** A config with two boards on the given subnet, and an optional explicit [network].bind_ip. */
35static fsw::config::Config makeConfig(const std::string& board_prefix,
36 const std::string& bind_ip = "") {
37 std::string toml;
38 if (!bind_ip.empty())
39 toml += "[network]\nbind_ip = \"" + bind_ip + "\"\n\n";
40 toml += "[boards.a]\ntype = \"PT\"\nip = \"" + board_prefix + ".21\"\nboard_id = 21\n";
41 toml += "enabled = true\n\n";
42 toml += "[boards.b]\ntype = \"PT\"\nip = \"" + board_prefix + ".22\"\nboard_id = 22\n";
43 toml += "enabled = true\n";
45}
46
47int main() {
48 std::cout << "=== DAQ interface resolution ===" << std::endl;
49
50 // 1. Auto-detect finds the interface holding the board subnet. Loopback holds 127.0.0.0/8, so
51 // boards at 127.0.0.x resolve to it — which is exactly how a sim run resolves.
52 {
53 const auto r = fsw::net::resolveDaqBindAddress(makeConfig("127.0.0"), "test");
54 check(r.ok, "auto: loopback boards resolve without error");
55 check(r.pinned, "auto: loopback boards are pinned to a NIC");
56 check(r.address.rfind("127.", 0) == 0,
57 "auto: pinned address is on loopback (got " + r.address + ")");
58 }
59
60 // 2. No interface on the board subnet — a dev laptop or a CI runner. Must stay soft: hard
61 // failing here would break ./dev.sh on every machine without a test stand attached.
62 {
63 const auto r = fsw::net::resolveDaqBindAddress(makeConfig("198.51.100"), "test");
64 check(r.ok, "no match: does not fail startup");
65 check(!r.pinned, "no match: reports itself as unpinned");
66 check(r.address == "0.0.0.0", "no match: falls back to 0.0.0.0");
67 }
68
69 // 3. An explicit [network].bind_ip wins over auto-detection.
70 {
71 const auto r = fsw::net::resolveDaqBindAddress(makeConfig("127.0.0", "127.0.0.1"), "test");
72 check(r.ok && r.pinned, "explicit: accepted");
73 check(r.address == "127.0.0.1", "explicit: uses the configured address");
74 }
75
76 // 4. An explicit bind_ip that is not on this host fails startup rather than degrading to
77 // 0.0.0.0. Silently ignoring the operator's pin is the original bug wearing a config key.
78 {
79 const auto r = fsw::net::resolveDaqBindAddress(makeConfig("127.0.0", "192.0.2.1"), "test");
80 check(!r.ok, "explicit but absent: refuses to start");
81 check(!r.pinned, "explicit but absent: not reported as pinned");
82 }
83
84 // 5. bindToDaqInterface on real sockets. "0.0.0.0" is a no-op returning success, so every
85 // caller can invoke it unconditionally and the dev path needs no special case.
86 {
87 int s1 = ::socket(AF_INET, SOCK_DGRAM, 0);
88 check(fsw::net::bindToDaqInterface(s1, "0.0.0.0", "test"), "bind: 0.0.0.0 is a no-op");
89 ::close(s1);
90
91 int s2 = ::socket(AF_INET, SOCK_DGRAM, 0);
92 check(fsw::net::bindToDaqInterface(s2, "127.0.0.1", "test"), "bind: a local address binds");
93 ::close(s2);
94
95 int s3 = ::socket(AF_INET, SOCK_DGRAM, 0);
96 check(!fsw::net::bindToDaqInterface(s3, "192.0.2.1", "test"),
97 "bind: an address not on this host fails rather than silently not binding");
98 ::close(s3);
99 }
100
101 std::cout << (g_failures ? "FAILED" : "PASSED") << std::endl;
102 return g_failures ? 1 : 0;
103}
static bool ok
Definition flight_e2e.c:435
Config load_from_string(const std::string &text)
Parse config from an in-memory TOML string (SequencerService / ActuatorCommander hold it so).
Definition Config.cpp:458
bool bindToDaqInterface(int sock, const std::string &address, const char *who)
bind() an already-created socket to address on an ephemeral port.
DaqBindResult resolveDaqBindAddress(const ::fsw::config::Config &cfg, const char *who)
Decide which local address board traffic should leave from, and log the decision.
The whole parsed config.toml, restricted to what C++ consumes.
Definition Config.hpp:264
static int g_failures
static boot_report_t r
static void check(bool ok, const std::string &what)
static fsw::config::Config makeConfig(const std::string &board_prefix, const std::string &bind_ip="")
A config with two boards on the given subnet, and an optional explicit [network].bind_ip.
int main()