summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Härdeman <david@hardeman.nu>2020-06-07 23:41:54 +0200
committerDavid Härdeman <david@hardeman.nu>2020-06-07 23:41:54 +0200
commit1e2f3e437492ecc841bc9852ab46ce0e218e4723 (patch)
tree0d58d87bf7c3c60b1b92139d1966af08f42d49af
parentf7804084f43403b1370851de9d459c49f55baa9a (diff)
Add basic support for checking idle status
-rw-r--r--cfgdir.c10
-rw-r--r--idle.c453
-rw-r--r--idle.h10
-rw-r--r--main.c2
-rw-r--r--meson.build10
-rw-r--r--server.c21
-rw-r--r--server.h7
7 files changed, 511 insertions, 2 deletions
diff --git a/cfgdir.c b/cfgdir.c
index c2870ab..120fa40 100644
--- a/cfgdir.c
+++ b/cfgdir.c
@@ -25,6 +25,7 @@ enum scfg_keys {
SCFG_KEY_PORT,
SCFG_KEY_LOCAL,
SCFG_KEY_REMOTE,
+ SCFG_KEY_IDLE,
};
struct cfg_key_value_map scfg_key_map[] = {
@@ -49,6 +50,10 @@ struct cfg_key_value_map scfg_key_map[] = {
.key_value = SCFG_KEY_REMOTE,
.value_type = CFG_VAL_TYPE_ADDRS,
}, {
+ .key_name = "idle",
+ .key_value = SCFG_KEY_IDLE,
+ .value_type = CFG_VAL_TYPE_UINT16,
+ }, {
.key_name = NULL,
.key_value = SCFG_KEY_INVALID,
.value_type = CFG_VAL_TYPE_INVALID,
@@ -116,6 +121,11 @@ scfg_parse(struct cfg *cfg, struct server *scfg)
break;
}
+ case SCFG_KEY_IDLE:
+ if (!server_set_idle(cfg, scfg, value.uint16))
+ return;
+ break;
+
case SCFG_KEY_INVALID:
default:
break;
diff --git a/idle.c b/idle.c
new file mode 100644
index 0000000..89a8f9a
--- /dev/null
+++ b/idle.c
@@ -0,0 +1,453 @@
+#define _GNU_SOURCE
+#include <inttypes.h>
+#include <sys/timerfd.h>
+#include <sys/socket.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "main.h"
+#include "uring.h"
+#include "config.h"
+#include "server.h"
+#include "idle.h"
+
+struct idle {
+ uint64_t value;
+ struct server *server;
+ struct uring_task task;
+
+ struct uring_task idlecheck;
+ unsigned next_remote;
+ struct sockaddr_in46 remote;
+ char remotestr[ADDRSTRLEN];
+ char remotebuf[4096];
+ size_t remotebuflen;
+ size_t remotebufdone;
+};
+
+static void
+idle_check_free(struct uring_task *task)
+{
+ struct idle *idle = container_of(task, struct idle, idlecheck);
+
+ fprintf(stderr, "%s: called with task 0x%p and idle 0x%p\n", __func__, task, idle);
+}
+
+static inline void
+write_byte(char **pos, char byte)
+{
+ **pos = byte;
+ (*pos)++;
+}
+
+#define MC_HELO 0x00
+#define MC_NEXT_STATE_STATUS 0x01
+#define MC_GET_STATUS 0x00
+#define MC_VARINT_MAX_BYTES 5
+#define MC_STATUS_REPLY 0x00
+
+static inline void
+write_varint(char **pos, int32_t orig)
+{
+ uint32_t val = (uint32_t)orig;
+
+ while (val) {
+ **pos = val & 0x7f;
+ val >>= 7;
+ if (val > 0)
+ **pos |= 0x80;
+ (*pos)++;
+ }
+}
+
+/*
+ * return value:
+ * positive = need more bytes
+ * zero = value decoded
+ * negative = error
+ */
+static inline int
+read_varint(char **pos, size_t *remain, int32_t *res)
+{
+ unsigned consumed;
+ uint32_t val = 0;
+
+ for (consumed = 1; consumed <= *remain; consumed++) {
+ uint32_t tmp;
+
+ tmp = **pos & 0x7f;
+ val += (tmp << (7 * (consumed - 1)));
+ (*pos)++;
+
+ if (!(tmp & 0x80))
+ break;
+ }
+
+ if (consumed > *remain)
+ return 1;
+ else if (consumed > MC_VARINT_MAX_BYTES)
+ return -1;
+
+ *remain -= consumed;
+ *res = (int32_t)val;
+ return 0;
+}
+
+static inline void
+write_bytes(char **pos, const char *bytes, size_t n)
+{
+ memcpy(*pos, bytes, n);
+ *pos += n;
+}
+
+static inline void
+write_str(char **pos, const char *str)
+{
+ write_bytes(pos, str, strlen(str));
+}
+
+static inline void
+write_cmd(char **pos, const char *begin, const char *end)
+{
+ write_varint(pos, end - begin);
+ write_bytes(pos, begin, end - begin);
+}
+
+#define JSON_NEEDLE "\"online\""
+static void
+idle_check_handshake_reply(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct idle *idle = container_of(task, struct idle, idlecheck);
+ int32_t mclen;
+ int32_t jsonlen;
+ char *pos;
+ size_t remain;
+ int r;
+
+ fprintf(stderr, "%s: received %i bytes\n", __func__, res);
+ if (res < 0)
+ goto error;
+
+ idle->remotebuflen += res;
+
+ /*
+ fprintf(stderr, "Received MC message (%i bytes):\n", res);
+ for (int i = 0; i < res; i++)
+ fprintf(stderr, "0x%02hhx ", idle->remotebuf[i]);
+ fprintf(stderr, "\n");
+ */
+
+ remain = idle->remotebuflen;
+ pos = idle->remotebuf;
+
+ r = read_varint(&pos, &remain, &mclen);
+ if (r < 0) {
+ fprintf(stderr, "Failed to parse message length\n");
+ goto error;
+ } else if (r > 0) {
+ goto read_more;
+ } else if (mclen < 2) {
+ fprintf(stderr, "Short MC message\n");
+ goto error;
+ }
+
+ fprintf(stderr, "MC message len: %" PRIi32 "\n", mclen);
+ fprintf(stderr, "Remain: %zu\n", remain);
+
+ if (mclen < remain)
+ goto read_more;
+
+ if (*pos != MC_STATUS_REPLY) {
+ fprintf(stderr, "Unknown server reply\n");
+ goto error;
+ }
+ pos++;
+ remain--;
+
+ r = read_varint(&pos, &remain, &jsonlen);
+ if (r != 0) {
+ fprintf(stderr, "Could not read JSON length\n");
+ goto error;
+ }
+
+ fprintf(stderr, "MC json len: %" PRIi32 "\n", jsonlen);
+ fprintf(stderr, "Remain: %zu\n", remain);
+
+ if (jsonlen < remain) {
+ fprintf(stderr, "Invalid JSON length\n");
+ goto error;
+ }
+
+ fprintf(stderr, "JSON: ");
+ for (int i = 0; i < jsonlen; i++)
+ fprintf(stderr, "%c", pos[i]);
+ fprintf(stderr, "\n");
+
+ /*
+ * Example JSON (line breaks added):
+ * {"description":{
+ * "text":"A Minecraft Server"},
+ * "players":{"max":20,"online":0},
+ * "version":{"name":"1.15.2","protocol":578}
+ * }
+ */
+
+ char *online;
+ char *end;
+ unsigned count;
+
+ online = memmem(pos, remain, JSON_NEEDLE, strlen(JSON_NEEDLE));
+ if (!online) {
+ fprintf(stderr, "Could not find online count in JSON\n");
+ goto error;
+ }
+
+ remain -= (online - pos);
+
+ end = memchr(online, '}', remain);
+ if (!end) {
+ fprintf(stderr, "Could not parse JSON (no end)\n");
+ goto error;
+ }
+ *end = '\0';
+
+ if (sscanf(online, JSON_NEEDLE " : %u", &count) != 1) {
+ fprintf(stderr, "Could not parse JSON (online count)\n");
+ goto error;
+ }
+
+ fprintf(stderr, "We have %u players\n", count);
+ if (count > 0)
+ idle->server->idle_count = 0;
+ else {
+ idle->server->idle_count++;
+ if (idle->server->idle_count > idle->server->idle_timeout)
+ fprintf(stderr, "Would shutdown idle server %s\n", idle->server->name);
+ }
+
+ return;
+
+read_more:
+ if (idle->remotebuflen >= sizeof(idle->remotebuflen))
+ goto error;
+
+ uring_read(cfg, &idle->idlecheck, idle->remotebuf + idle->remotebuflen,
+ sizeof(idle->remotebuf) - idle->remotebuflen,
+ 0, idle_check_handshake_reply);
+ return;
+
+error:
+ /* FIXME */
+ return;
+}
+
+static void
+idle_check_handshake_sent(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct idle *idle = container_of(task, struct idle, idlecheck);
+
+ fprintf(stderr, "%s: sent %i bytes\n", __func__, res);
+ if (res < 0) {
+ return;
+ }
+
+ idle->remotebufdone += res;
+ if (idle->remotebufdone < idle->remotebuflen)
+ uring_write(cfg, &idle->idlecheck,
+ idle->remotebuf + idle->remotebufdone,
+ idle->remotebuflen - idle->remotebufdone,
+ idle_check_handshake_sent);
+ else {
+ idle->remotebuflen = 0;
+ uring_read(cfg, &idle->idlecheck, idle->remotebuf, sizeof(idle->remotebuf), 0, idle_check_handshake_reply);
+ }
+}
+
+static void idle_check_connect_next_remote(struct cfg *cfg, struct idle *idle);
+
+static void
+idle_check_remote_connected(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct idle *idle = container_of(task, struct idle, idlecheck);
+ char buf[1024];
+ char *pos;
+ char *cmdbuf = idle->remotebuf;
+ uint16_t port = 25565;
+
+ fprintf(stderr, "%s: connected %i\n", __func__, res);
+ if (res < 0) {
+ idle_check_connect_next_remote(cfg, idle);
+ return;
+ }
+
+ pos = buf;
+ write_byte(&pos, MC_HELO);
+ write_varint(&pos, -1); /* Protocol version, -1 = undefined */
+ write_varint(&pos, strlen("hostname"));
+ write_str(&pos, "hostname");
+ write_byte(&pos, (port >> 8) & 0xff);
+ write_byte(&pos, (port >> 0) & 0xff);
+ write_byte(&pos, MC_NEXT_STATE_STATUS);
+ write_cmd(&cmdbuf, buf, pos);
+
+ pos = buf;
+ write_byte(&pos, MC_GET_STATUS);
+ write_cmd(&cmdbuf, buf, pos);
+
+ idle->remotebufdone = 0;
+ idle->remotebuflen = (cmdbuf - idle->remotebuf);
+ fprintf(stderr, "Sending MC message (%zu bytes):\n", idle->remotebuflen);
+ for (pos = idle->remotebuf; pos < cmdbuf; pos++)
+ fprintf(stderr, "0x%02hhx ", *pos);
+ fprintf(stderr, "\n");
+
+ uring_write(cfg, &idle->idlecheck, idle->remotebuf, idle->remotebuflen, idle_check_handshake_sent);
+}
+
+/* FIXME: Parts of this could be shared with proxy.c, probably in server.c */
+static void
+idle_check_connect_next_remote(struct cfg *cfg, struct idle *idle)
+{
+ struct sockaddr_in46 *remote, *tmp;
+ struct server *scfg = idle->server;
+ int sfd;
+ unsigned i = 0;
+
+again:
+ remote = NULL;
+ list_for_each_entry(tmp, &scfg->remotes, list) {
+ if (i == idle->next_remote) {
+ remote = tmp;
+ break;
+ }
+ i++;
+ }
+
+ if (!remote) {
+ fprintf(stderr, "No more remote addresses to attempt\n");
+ /* FIXME: put tasks */
+ return;
+ }
+
+ idle->next_remote++;
+ idle->remote = *remote;
+ sockaddr_to_str(&idle->remote, idle->remotestr, sizeof(idle->remotestr));
+ fprintf(stderr, "%s: attempting idle check on %s (len %u)\n",
+ scfg->name, idle->remotestr, idle->remote.addrlen);
+
+ sfd = socket(idle->remote.storage.ss_family, SOCK_STREAM, 0);
+ if (sfd < 0) {
+ perror("socket");
+ goto again;
+ }
+
+ uring_task_set_fd(&idle->idlecheck, sfd);
+ uring_connect(cfg, &idle->idlecheck, &idle->remote, idle_check_remote_connected);
+}
+
+static void
+idle_cb(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct idle *idle = container_of(task, struct idle, task);
+
+ fprintf(stderr, "%s: ret is %i (ref %u)\n", __func__, res, task->refcount);
+
+ if (task->dead) {
+ fprintf(stderr, "%s: task is dead\n", __func__);
+ uring_task_put(cfg, task);
+ return;
+ }
+
+ if (res != sizeof(idle->value))
+ perrordie("timerfd_read");
+
+ fprintf(stderr, "%s: called with value %" PRIu64 "\n", __func__, idle->value);
+
+ idle->next_remote = 0;
+ if (!list_empty(&idle->server->proxys))
+ idle->server->idle_count = 0;
+ else
+ idle_check_connect_next_remote(cfg, idle);
+ uring_read(cfg, &idle->task, &idle->value, sizeof(idle->value), 0, idle_cb);
+}
+
+static void
+idle_free(struct uring_task *task)
+{
+ struct idle *idle = container_of(task, struct idle, task);
+
+ fprintf(stderr, "%s: called with task 0x%p and idle 0x%p\n", __func__, task, idle);
+ idle->server->idle = NULL;
+ free(idle);
+}
+
+void
+idle_refdump(struct idle *idle)
+{
+ if (!idle)
+ return;
+
+ uring_task_refdump(&idle->task);
+ uring_task_refdump(&idle->idlecheck);
+}
+
+void
+idle_delete(struct cfg *cfg, struct server *server)
+{
+ struct idle *idle = server->idle;
+
+ if (!idle)
+ return;
+
+ fprintf(stderr, "%s called, closing fd %i\n", __func__, idle->task.fd);
+ uring_cancel(cfg, &idle->task);
+ uring_task_put(cfg, &idle->task);
+ uring_task_put(cfg, &idle->idlecheck);
+ server->idle = NULL;
+}
+
+void
+idle_init(struct cfg *cfg, struct server *server)
+{
+ struct idle *idle;
+ int ifd;
+ struct itimerspec tspec = {
+ .it_interval = {
+ .tv_sec = 60,
+ .tv_nsec = 0
+ },
+ .it_value = {
+ /* FIXME: change to 60 */
+ .tv_sec = 4,
+ .tv_nsec = 0
+ }
+ };
+
+ if (!server)
+ return;
+
+ if (server->idle_timeout < 1)
+ return;
+
+ idle = zmalloc(sizeof(*idle));
+ if (!idle)
+ perrordie("malloc");
+
+ ifd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
+ if (ifd < 0)
+ perrordie("timerfd_create");
+
+ if (timerfd_settime(ifd, 0, &tspec, NULL) != 0)
+ perrordie("timerfd_settime");
+
+ uring_task_init(&idle->task, "idle", &server->task, idle_free);
+ uring_task_init(&idle->idlecheck, "idlecheck", &idle->task, idle_check_free);
+ uring_task_set_fd(&idle->task, ifd);
+ idle->server = server;
+ server->idle = idle;
+
+ uring_read(cfg, &idle->task, &idle->value, sizeof(idle->value), 0, idle_cb);
+}
+
diff --git a/idle.h b/idle.h
new file mode 100644
index 0000000..d15ee9b
--- /dev/null
+++ b/idle.h
@@ -0,0 +1,10 @@
+#ifndef fooidlehfoo
+#define fooidlehfoo
+
+void idle_refdump(struct idle *idle);
+
+void idle_delete(struct cfg *cfg, struct server *server);
+
+void idle_init(struct cfg *cfg, struct server *server);
+
+#endif
diff --git a/main.c b/main.c
index 398a998..da962b4 100644
--- a/main.c
+++ b/main.c
@@ -303,7 +303,7 @@ main(int argc, char **argv)
announce_init(cfg);
- announce_start(cfg->aev);
+ //announce_start(cfg->aev);
uring_task_put(cfg, &cfg->task);
diff --git a/meson.build b/meson.build
index 051cdd2..85dbab9 100644
--- a/meson.build
+++ b/meson.build
@@ -4,6 +4,14 @@ uring = dependency('liburing')
executable('ctest', 'ctest.c')
executable('stest', 'stest.c')
executable('mcproxy',
- ['main.c', 'uring.c', 'server.c', 'proxy.c', 'announce.c', 'cfgdir.c', 'config.c', 'utils.c'],
+ ['main.c',
+ 'uring.c',
+ 'server.c',
+ 'proxy.c',
+ 'announce.c',
+ 'cfgdir.c',
+ 'config.c',
+ 'idle.c',
+ 'utils.c'],
dependencies: uring)
diff --git a/server.c b/server.c
index 739fecf..77787fb 100644
--- a/server.c
+++ b/server.c
@@ -9,6 +9,7 @@
#include "server.h"
#include "proxy.h"
#include "utils.h"
+#include "idle.h"
struct server_local {
struct sockaddr_in46 addr;
@@ -29,6 +30,8 @@ server_refdump(struct server *server)
uring_task_refdump(&local->task);
list_for_each_entry(proxy, &server->proxys, list)
proxy_refdump(proxy);
+ if (server->idle)
+ idle_refdump(server->idle);
}
static void
@@ -51,6 +54,8 @@ server_delete(struct cfg *cfg, struct server *scfg)
fprintf(stderr, "Removing server cfg: %s\n", scfg->name);
+ idle_delete(cfg, scfg);
+
list_for_each_entry_safe(remote, rtmp, &scfg->remotes, list) {
list_del(&remote->list);
free(remote);
@@ -217,6 +222,8 @@ server_commit(struct cfg *cfg, struct server *scfg)
case SERVER_TYPE_ANNOUNCE:
if (scfg->announce_port < 1)
return false;
+ if (scfg->idle_timeout > 0)
+ return false;
if (!list_empty(&scfg->locals))
return false;
if (!list_empty(&scfg->remotes))
@@ -273,6 +280,9 @@ server_commit(struct cfg *cfg, struct server *scfg)
list_for_each_entry(local, &scfg->locals, list) {
server_local_open(cfg, scfg, local);
}
+
+ idle_init(cfg, scfg);
+
return true;
}
@@ -309,6 +319,16 @@ server_add_local(struct cfg *cfg, struct server *scfg, struct sockaddr_in46 *add
}
bool
+server_set_idle(struct cfg *cfg, struct server *scfg, uint16_t timeout)
+{
+ if (!scfg || scfg->idle_timeout != 0)
+ return false;
+
+ scfg->idle_timeout = timeout;
+ return true;
+}
+
+bool
server_set_port(struct cfg *cfg, struct server *scfg, uint16_t port)
{
if (!scfg || scfg->announce_port != 0)
@@ -382,6 +402,7 @@ server_new(struct cfg *cfg, const char *name)
memset(&scfg->mcast_msg, 0, sizeof(scfg->mcast_msg));
scfg->mcast_msg.msg_iov = &scfg->mcast_iov;
scfg->mcast_msg.msg_iovlen = 1;
+ scfg->idle_timeout = 0;
list_add(&scfg->list, &cfg->servers);
return scfg;
diff --git a/server.h b/server.h
index a6936cd..2e431dc 100644
--- a/server.h
+++ b/server.h
@@ -26,6 +26,11 @@ struct server {
struct msghdr mcast_msg;
char mcast_buf[4096];
+ /* For checking idle status */
+ struct idle *idle;
+ unsigned idle_timeout;
+ unsigned idle_count;
+
struct uring_task task;
struct list_head list;
};
@@ -44,6 +49,8 @@ bool server_add_remote(struct cfg *cfg, struct server *scfg,
bool server_add_local(struct cfg *cfg, struct server *scfg,
struct sockaddr_in46 *local);
+bool server_set_idle(struct cfg *cfg, struct server *scfg, uint16_t timeout);
+
bool server_set_port(struct cfg *cfg, struct server *scfg, uint16_t port);
bool server_set_type(struct cfg *cfg, struct server *scfg,