e1608: discover boards on every interface, not only the default route's

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-02 10:14:47 +01:00
1 parent 9426a61919
commit 43c21a0cc2
3 files changed
+112 -13

No files matched your search

+82 -12
View File
@@ -9,11 +9,14 @@
#include <chrono>
#include <cstdio>
#include <cstring>
#include <ifaddrs.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <sys/socket.h>
#include <thread>
#include <unistd.h>
namespace udpscope::e1608 {
@@ -120,6 +123,25 @@ Fd tcp_connect(const std::string& host, int port, int timeout_ms, int rcvbuf) {
return fd;
}
/// A `D` reply (at least 29 bytes, first byte `D`, our product id). True when it added a board
/// that `found` did not already hold (one entry per MAC).
bool take_reply(const std::uint8_t* buf, ssize_t n, const sockaddr_in& from,
std::vector<DeviceInfo>& found) {
if (n < 29 || buf[0] != 'D') return false;
DeviceInfo info;
info.product_id = static_cast<std::uint16_t>(buf[7] | (buf[8] << 8));
if (info.product_id != kProductId) return false;
std::copy(buf + 1, buf + 7, info.mac.begin());
for (const DeviceInfo& d : found) {
if (d.mac == info.mac) return false;
}
char ip[INET_ADDRSTRLEN] = {};
::inet_ntop(AF_INET, &from.sin_addr, ip, sizeof ip);
info.ip = ip;
found.push_back(info);
return true;
}
} // namespace
// --- addresses ------------------------------------------------------------------------------
@@ -222,22 +244,70 @@ std::vector<DeviceInfo> discover(const std::vector<std::string>& targets, int po
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue;
break;
}
if (n < 29 || buf[0] != 'D') continue;
DeviceInfo info;
info.product_id = static_cast<std::uint16_t>(buf[7] | (buf[8] << 8));
if (info.product_id != kProductId) continue;
std::copy(buf + 1, buf + 7, info.mac.begin());
bool dup = false;
for (const DeviceInfo& d : found) dup = dup || d.mac == info.mac;
if (dup) continue;
char ip[INET_ADDRSTRLEN] = {};
::inet_ntop(AF_INET, &from.sin_addr, ip, sizeof ip);
info.ip = ip;
found.push_back(info);
take_reply(buf, n, from, found);
}
return found;
}
Scan::Scan(int port) {
ifaddrs* list = nullptr;
if (::getifaddrs(&list) != 0) return;
sockaddr_in to{};
to.sin_family = AF_INET;
to.sin_port = htons(static_cast<std::uint16_t>(port));
to.sin_addr.s_addr = htonl(INADDR_BROADCAST);
const std::uint8_t probe = 'D';
std::vector<std::string> done; // an interface with several addresses needs one probe, not several
for (const ifaddrs* a = list; a != nullptr; a = a->ifa_next) {
if (a->ifa_addr == nullptr || a->ifa_addr->sa_family != AF_INET) continue;
const unsigned fl = a->ifa_flags;
if ((fl & IFF_UP) == 0 || (fl & IFF_LOOPBACK) != 0 || (fl & IFF_BROADCAST) == 0) continue;
const std::string name = a->ifa_name;
if (std::find(done.begin(), done.end(), name) != done.end()) continue;
Fd s(::socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0));
if (!s.valid()) continue;
const int one = 1;
::setsockopt(s.get(), SOL_SOCKET, SO_BROADCAST, &one, sizeof one);
if (::setsockopt(s.get(), SOL_SOCKET, SO_BINDTODEVICE, name.c_str(),
static_cast<socklen_t>(name.size())) != 0) {
sockaddr_in me = *reinterpret_cast<const sockaddr_in*>(a->ifa_addr);
me.sin_port = 0;
if (::bind(s.get(), reinterpret_cast<const sockaddr*>(&me), sizeof me) != 0) continue;
}
if (::sendto(s.get(), &probe, 1, MSG_NOSIGNAL, reinterpret_cast<const sockaddr*>(&to),
sizeof to) != 1)
continue;
done.push_back(name);
socks_.push_back(std::move(s));
}
::freeifaddrs(list);
}
const std::vector<DeviceInfo>& Scan::poll() {
for (const Fd& s : socks_) {
for (;;) {
std::uint8_t buf[128];
sockaddr_in from{};
socklen_t flen = sizeof from;
const ssize_t n = ::recvfrom(s.get(), buf, sizeof buf, MSG_DONTWAIT,
reinterpret_cast<sockaddr*>(&from), &flen);
if (n < 0) break; // nothing more on this socket for now
take_reply(buf, n, from, found_);
}
}
return found_;
}
std::vector<DeviceInfo> discover_broadcast(int port, int wait_ms) {
Scan scan(port);
const auto deadline = Clock::now() + std::chrono::milliseconds(wait_ms);
while (ms_left(deadline) > 0) {
scan.poll();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
return scan.poll();
}
// --- Link -----------------------------------------------------------------------------------
void Link::connect(const std::string& host, int port, std::uint32_t code, int timeout_ms) {