Add UDP discovery and heartbeat-miss notices to remchild
This commit is contained in:
+154
-16
@@ -31,6 +31,10 @@
|
||||
|
||||
#include "words.h"
|
||||
|
||||
#define DISCOVERY_PORT 8798
|
||||
#define DISCOVERY_WINDOW_MS 5000
|
||||
#define DISCOVERY_MAX 8
|
||||
|
||||
#define HEARTBEAT_FAST_MS 10000
|
||||
#define HEARTBEAT_MID_MS 30000
|
||||
#define HEARTBEAT_SLOW_MS 300000
|
||||
@@ -38,6 +42,12 @@
|
||||
#define MID_UNTIL_MS 360000
|
||||
#define LINE_MAX 4096
|
||||
|
||||
static long heartbeat_interval(long elapsed_ms) {
|
||||
if (elapsed_ms < FAST_UNTIL_MS) return HEARTBEAT_FAST_MS;
|
||||
if (elapsed_ms < MID_UNTIL_MS) return HEARTBEAT_MID_MS;
|
||||
return HEARTBEAT_SLOW_MS;
|
||||
}
|
||||
|
||||
static long now_ms(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
@@ -104,6 +114,100 @@ static void name_for_id(const unsigned char *id, size_t len, char *out, size_t c
|
||||
snprintf(out, cap, "%s %s", REMCHILD_FIRST[first], REMCHILD_SECOND[second]);
|
||||
}
|
||||
|
||||
struct Discovered {
|
||||
char ip[64];
|
||||
char name[64];
|
||||
char id[64];
|
||||
int port;
|
||||
};
|
||||
|
||||
/*
|
||||
* Ask the LAN who is there. Probes go out as broadcast and to loopback; a
|
||||
* server answers with its beacon, and the address comes from the reply.
|
||||
* Waits up to DISCOVERY_WINDOW_MS. Returns the number of servers found.
|
||||
*/
|
||||
static int discover(struct Discovered *found, int cap) {
|
||||
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (fd < 0) return 0;
|
||||
|
||||
int one = 1;
|
||||
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one));
|
||||
|
||||
struct sockaddr_in local = {0};
|
||||
local.sin_family = AF_INET;
|
||||
local.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
if (bind(fd, (struct sockaddr *)&local, sizeof(local)) != 0) {
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct sockaddr_in broadcast = {0};
|
||||
broadcast.sin_family = AF_INET;
|
||||
broadcast.sin_port = htons(DISCOVERY_PORT);
|
||||
broadcast.sin_addr.s_addr = htonl(INADDR_BROADCAST);
|
||||
|
||||
struct sockaddr_in loopback = broadcast;
|
||||
loopback.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
|
||||
const char *probe = "{\"type\":\"veskslop-remchild-probe\",\"version\":\"0.1\"}";
|
||||
long deadline = now_ms() + DISCOVERY_WINDOW_MS;
|
||||
long next_probe = 0;
|
||||
int count = 0;
|
||||
|
||||
while (now_ms() < deadline) {
|
||||
if (now_ms() >= next_probe) {
|
||||
sendto(fd, probe, strlen(probe), 0, (struct sockaddr *)&broadcast, sizeof(broadcast));
|
||||
sendto(fd, probe, strlen(probe), 0, (struct sockaddr *)&loopback, sizeof(loopback));
|
||||
next_probe = now_ms() + 1000;
|
||||
}
|
||||
|
||||
struct pollfd pfd = {fd, POLLIN, 0};
|
||||
if (poll(&pfd, 1, 200) <= 0) continue;
|
||||
if (!(pfd.revents & POLLIN)) continue;
|
||||
|
||||
char buffer[1024];
|
||||
struct sockaddr_in from;
|
||||
socklen_t from_len = sizeof(from);
|
||||
ssize_t got = recvfrom(fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&from, &from_len);
|
||||
if (got <= 0) continue;
|
||||
buffer[got] = 0;
|
||||
|
||||
if (!strstr(buffer, "veskslop-remchild") || strstr(buffer, "probe")) continue;
|
||||
|
||||
char ip[64];
|
||||
if (!inet_ntop(AF_INET, &from.sin_addr, ip, sizeof(ip))) continue;
|
||||
|
||||
int port = 0;
|
||||
char name[64] = "veskslop";
|
||||
char id[64] = "";
|
||||
char *match = strstr(buffer, "\"port\":");
|
||||
if (match) port = atoi(match + 7);
|
||||
match = strstr(buffer, "\"name\":\"");
|
||||
if (match) sscanf(match + 8, "%63[^\"]", name);
|
||||
match = strstr(buffer, "\"id\":\"");
|
||||
if (match) sscanf(match + 6, "%63[^\"]", id);
|
||||
if (port <= 0) continue;
|
||||
|
||||
// One server can answer from both loopback and its LAN address; the
|
||||
// beacon id identifies it.
|
||||
int duplicate = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (id[0] && found[i].id[0] && !strcmp(found[i].id, id)) duplicate = 1;
|
||||
else if (!id[0] && !found[i].id[0] && !strcmp(found[i].name, name) && found[i].port == port) duplicate = 1;
|
||||
}
|
||||
if (duplicate || count >= cap) continue;
|
||||
|
||||
snprintf(found[count].ip, sizeof(found[count].ip), "%s", ip);
|
||||
snprintf(found[count].name, sizeof(found[count].name), "%s", name);
|
||||
snprintf(found[count].id, sizeof(found[count].id), "%s", id);
|
||||
found[count].port = port;
|
||||
count++;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return count;
|
||||
}
|
||||
|
||||
static int connect_to(const char *host, int port) {
|
||||
struct sockaddr_in addr = {0};
|
||||
addr.sin_family = AF_INET;
|
||||
@@ -159,17 +263,43 @@ int main(int argc, char **argv) {
|
||||
int interactive = isatty(STDIN_FILENO);
|
||||
|
||||
if (!host[0]) {
|
||||
printf("Server address (host:port): ");
|
||||
struct Discovered found[DISCOVERY_MAX];
|
||||
printf("[remchild] looking for a server on the LAN for %d seconds...\n", DISCOVERY_WINDOW_MS / 1000);
|
||||
fflush(stdout);
|
||||
char line[128];
|
||||
if (!fgets(line, sizeof(line), stdin)) return 1;
|
||||
line[strcspn(line, "\n")] = 0;
|
||||
char *colon = strrchr(line, ':');
|
||||
if (colon) {
|
||||
*colon = 0;
|
||||
port = atoi(colon + 1);
|
||||
int count = discover(found, DISCOVERY_MAX);
|
||||
|
||||
if (count == 1) {
|
||||
printf("[remchild] found %s at %s:%d\n", found[0].name, found[0].ip, found[0].port);
|
||||
snprintf(host, sizeof(host), "%s", found[0].ip);
|
||||
port = found[0].port;
|
||||
} else if (count > 1) {
|
||||
printf("[remchild] found %d servers:\n", count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
printf(" %d) %s at %s:%d\n", i + 1, found[i].name, found[i].ip, found[i].port);
|
||||
}
|
||||
printf("pick one [1]: ");
|
||||
fflush(stdout);
|
||||
char pick_line[32];
|
||||
int pick = 1;
|
||||
if (fgets(pick_line, sizeof(pick_line), stdin)) pick = atoi(pick_line);
|
||||
if (pick < 1 || pick > count) pick = 1;
|
||||
printf("[remchild] using %s at %s:%d\n", found[pick - 1].name, found[pick - 1].ip, found[pick - 1].port);
|
||||
snprintf(host, sizeof(host), "%s", found[pick - 1].ip);
|
||||
port = found[pick - 1].port;
|
||||
} else {
|
||||
printf("[remchild] no server answered discovery.\n");
|
||||
printf("Server address (host:port): ");
|
||||
fflush(stdout);
|
||||
char line[128];
|
||||
if (!fgets(line, sizeof(line), stdin)) return 1;
|
||||
line[strcspn(line, "\n")] = 0;
|
||||
char *colon = strrchr(line, ':');
|
||||
if (colon) {
|
||||
*colon = 0;
|
||||
port = atoi(colon + 1);
|
||||
}
|
||||
snprintf(host, sizeof(host), "%s", line);
|
||||
}
|
||||
snprintf(host, sizeof(host), "%s", line);
|
||||
}
|
||||
|
||||
unsigned char id[32];
|
||||
@@ -196,6 +326,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
printf("[remchild] identity %s (name: %s)\n", id_len == 6 ? "MAC" : "machine-id", name);
|
||||
printf("[remchild] connecting to %s:%d\n", host, port);
|
||||
fflush(stdout);
|
||||
|
||||
int fd = connect_to(host, port);
|
||||
if (fd < 0) {
|
||||
@@ -214,8 +345,9 @@ int main(int argc, char **argv) {
|
||||
|
||||
char hello[512];
|
||||
snprintf(hello, sizeof(hello),
|
||||
"{\"type\":\"hello\",\"id\":\"%s\",\"name\":\"%s\",\"platform\":\"linux\",\"version\":\"0.1\"}",
|
||||
id_hex, name);
|
||||
"{\"type\":\"hello\",\"id\":\"%s\",\"name\":\"%s\",\"platform\":\"linux\","
|
||||
"\"version\":\"0.1\",\"heartbeatMs\":%ld}",
|
||||
id_hex, name, heartbeat_interval(0));
|
||||
send_line(fd, hello);
|
||||
|
||||
long started = now_ms();
|
||||
@@ -234,13 +366,12 @@ int main(int argc, char **argv) {
|
||||
|
||||
for (;;) {
|
||||
long now = now_ms();
|
||||
long interval = now - started < FAST_UNTIL_MS ? HEARTBEAT_FAST_MS
|
||||
: now - started < MID_UNTIL_MS ? HEARTBEAT_MID_MS
|
||||
: HEARTBEAT_SLOW_MS;
|
||||
long interval = heartbeat_interval(now - started);
|
||||
|
||||
if (now - last_beat >= interval) {
|
||||
char beat[128];
|
||||
snprintf(beat, sizeof(beat), "{\"type\":\"heartbeat\",\"ts\":%ld}", (long)time(NULL));
|
||||
snprintf(beat, sizeof(beat), "{\"type\":\"heartbeat\",\"ts\":%ld,\"heartbeatMs\":%ld}",
|
||||
(long)time(NULL), interval);
|
||||
send_line(fd, beat);
|
||||
last_beat = now;
|
||||
}
|
||||
@@ -272,9 +403,16 @@ int main(int argc, char **argv) {
|
||||
} else if (strstr(line, "\"type\":\"pong\"")) {
|
||||
if (ping_sent_at) printf("[remchild] pong seq=%d rtt=%ldms\n", ping_seq, now_ms() - ping_sent_at);
|
||||
answered_ping = 1;
|
||||
} else if (strstr(line, "\"type\":\"notice\"")) {
|
||||
int missed = 0;
|
||||
char *value = strstr(line, "\"missed\":");
|
||||
if (value) missed = atoi(value + 9);
|
||||
printf("[remchild] notice: the server missed %d heartbeat(s); the connection may be unstable.\n",
|
||||
missed);
|
||||
} else if (strstr(line, "\"type\":\"ping\"")) {
|
||||
int seq = 0;
|
||||
sscanf(line, "%*[^\"]\"seq\":%d", &seq);
|
||||
char *value = strstr(line, "\"seq\":");
|
||||
if (value) seq = atoi(value + 6);
|
||||
char pong[64];
|
||||
snprintf(pong, sizeof(pong), "{\"type\":\"pong\",\"seq\":%d}", seq);
|
||||
send_line(fd, pong);
|
||||
|
||||
Reference in New Issue
Block a user