STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
DaqInterface.cpp
Go to the documentation of this file.
2
3#include <arpa/inet.h>
4#include <ifaddrs.h>
5#include <net/if.h>
6#include <netinet/in.h>
7#include <sys/socket.h>
8#include <unistd.h>
9
10#include <cerrno>
11#include <cstring>
12#include <iostream>
13#include <sstream>
14#include <string>
15#include <vector>
16
17namespace fsw {
18namespace net {
19
20namespace {
21
22bool isUnpinned(const std::string& a) {
23 return a.empty() || a == "0.0.0.0";
24}
25
26/** Parse dotted-quad into host-order uint32. Returns false on anything else. */
27bool parseV4(const std::string& s, uint32_t& out) {
28 struct in_addr a{};
29 if (inet_pton(AF_INET, s.c_str(), &a) != 1)
30 return false;
31 out = ntohl(a.s_addr);
32 return true;
33}
34
35/** Can this address actually be bound on this host? The only honest test is to try. */
36bool canBind(const std::string& address) {
37 int s = ::socket(AF_INET, SOCK_DGRAM, 0);
38 if (s < 0)
39 return false;
40 struct sockaddr_in local{};
41 local.sin_family = AF_INET;
42 local.sin_port = 0;
43 if (inet_pton(AF_INET, address.c_str(), &local.sin_addr) != 1) {
44 ::close(s);
45 return false;
46 }
47 const bool ok = ::bind(s, reinterpret_cast<struct sockaddr*>(&local), sizeof(local)) == 0;
48 ::close(s);
49 return ok;
50}
51
52/**
53 * The board addresses we are trying to reach. Only explicitly configured IPs are used: the
54 * synthesized "192.168.2.<board_id>" fallback some services apply would hardcode the very subnet
55 * this function exists to discover, and would make the sim (127.0.0.x boards) resolve to the
56 * hardware LAN.
57 */
58std::vector<uint32_t> boardAddresses(const ::fsw::config::Config& cfg) {
59 std::vector<uint32_t> out;
60 for (const auto& b : cfg.boards) {
61 if (!b.enabled || b.ip.empty())
62 continue;
63 uint32_t v = 0;
64 if (parseV4(b.ip, v))
65 out.push_back(v);
66 }
67 // No board carries an explicit IP (a minimal config, as the unit tests write). The broadcast
68 // destination names the same subnet, so it answers the same question.
69 if (out.empty()) {
70 uint32_t v = 0;
71 if (parseV4(cfg.server_heartbeat.broadcast_ip, v) && v != 0xFFFFFFFFu)
72 out.push_back(v);
73 }
74 return out;
75}
76
77struct Candidate {
78 std::string iface;
79 std::string address;
80};
81
82} // namespace
83
84// ─────────────────────────────────────────────────────────────────────────────
85DaqBindResult resolveDaqBindAddress(const ::fsw::config::Config& cfg, const char* who) {
87
88 // 1. Explicit pin wins, and must work.
89 if (!isUnpinned(cfg.network.bind_ip)) {
90 r.address = cfg.network.bind_ip;
91 if (!canBind(r.address)) {
92 r.ok = false;
93 r.detail = "[network].bind_ip = " + r.address + " is not an address on this host";
94 std::cerr << "[" << who << "] " << r.detail
95 << " — refusing to start. Board traffic would leave on an interface nobody "
96 "chose. Fix the NIC or clear the key to auto-detect."
97 << std::endl;
98 return r;
99 }
100 r.pinned = true;
101 r.detail = "board traffic pinned to " + r.address + " (explicit [network].bind_ip)";
102 std::cout << "[" << who << "] " << r.detail << std::endl;
103 return r;
104 }
105
106 // 2. Auto-detect: the interface whose subnet holds the configured boards.
107 const std::vector<uint32_t> boards = boardAddresses(cfg);
108 std::vector<Candidate> matches;
109
110 struct ifaddrs* ifa_head = nullptr;
111 if (getifaddrs(&ifa_head) == 0) {
112 for (struct ifaddrs* ifa = ifa_head; ifa; ifa = ifa->ifa_next) {
113 if (!ifa->ifa_addr || !ifa->ifa_netmask || ifa->ifa_addr->sa_family != AF_INET)
114 continue;
115 if (!(ifa->ifa_flags & IFF_UP))
116 continue;
117
118 const uint32_t addr =
119 ntohl(reinterpret_cast<struct sockaddr_in*>(ifa->ifa_addr)->sin_addr.s_addr);
120 const uint32_t mask =
121 ntohl(reinterpret_cast<struct sockaddr_in*>(ifa->ifa_netmask)->sin_addr.s_addr);
122
123 bool holds_a_board = false;
124 for (uint32_t b : boards) {
125 if ((b & mask) == (addr & mask)) {
126 holds_a_board = true;
127 break;
128 }
129 }
130 if (!holds_a_board)
131 continue;
132
133 // A NIC with two addresses in the subnet is not ambiguous — egress is the same
134 // interface either way — so keep the first and do not let it trip the check below.
135 const std::string name = ifa->ifa_name ? ifa->ifa_name : "?";
136 bool already = false;
137 for (const auto& c : matches)
138 if (c.iface == name)
139 already = true;
140 if (already)
141 continue;
142
143 char buf[INET_ADDRSTRLEN] = {0};
144 struct in_addr net_order{};
145 net_order.s_addr = htonl(addr);
146 inet_ntop(AF_INET, &net_order, buf, sizeof(buf));
147 matches.push_back({name, buf});
148 }
149 freeifaddrs(ifa_head);
150 }
151
152 // 3. Ambiguous — say so rather than guess. Guessing is the bug.
153 if (matches.size() > 1) {
154 std::ostringstream os;
155 os << "board subnet is reachable on " << matches.size() << " interfaces (";
156 for (size_t i = 0; i < matches.size(); ++i)
157 os << (i ? ", " : "") << matches[i].iface << " " << matches[i].address;
158 os << ")";
159 r.ok = false;
160 r.detail = os.str();
161 std::cerr << "[" << who << "] " << r.detail
162 << " — refusing to start. Set [network].bind_ip to the one board traffic should "
163 "use."
164 << std::endl;
165 return r;
166 }
167
168 if (matches.size() == 1) {
169 r.address = matches[0].address;
170 r.pinned = true;
171 r.detail = "board traffic pinned to " + matches[0].iface + " " + r.address + " (auto)";
172 std::cout << "[" << who << "] " << r.detail << std::endl;
173 return r;
174 }
175
176 // 4. Nothing on the board subnet: a dev laptop or CI. Stay unpinned — hard-failing here would
177 // break ./dev.sh, and there is no wrong wire to leave on when there is only one.
178 r.address = "0.0.0.0";
179 r.pinned = false;
180 r.detail = "no local interface on the board subnet — board traffic is NOT pinned to a NIC";
181 std::cout << "[" << who << "] " << r.detail
182 << " (expected on a dev box; on the rig this means the 192.168.2.x NIC is missing)"
183 << std::endl;
184 return r;
185}
186
187// ─────────────────────────────────────────────────────────────────────────────
188bool bindToDaqInterface(int sock, const std::string& address, const char* who) {
189 if (isUnpinned(address))
190 return true; // dev path — nothing to pin to, whatever the socket is
191 if (sock < 0)
192 return false;
193
194 struct sockaddr_in local{};
195 local.sin_family = AF_INET;
196 local.sin_port = 0; // ephemeral; we are choosing an interface, not a port
197 if (inet_pton(AF_INET, address.c_str(), &local.sin_addr) != 1) {
198 std::cerr << "[" << who << "] bad DAQ bind address '" << address << "'" << std::endl;
199 return false;
200 }
201 if (::bind(sock, reinterpret_cast<struct sockaddr*>(&local), sizeof(local)) < 0) {
202 std::cerr << "[" << who << "] bind to DAQ interface " << address
203 << " failed: " << strerror(errno) << std::endl;
204 return false;
205 }
206 return true;
207}
208
209} // namespace net
210} // namespace fsw
static bool ok
Definition flight_e2e.c:435
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
Definition json.hpp:13878
*brief create JSON pointer *sa reference_tokens const string_t & a
Definition json.hpp:13877
std::vector< uint32_t > boardAddresses(const ::fsw::config::Config &cfg)
The board addresses we are trying to reach.
bool parseV4(const std::string &s, uint32_t &out)
Parse dotted-quad into host-order uint32.
bool canBind(const std::string &address)
Can this address actually be bound on this host?
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.
Motorized ball valve position: raw ADC counts -> % open.
Which local NIC board traffic leaves from.
static boot_report_t r