From 1e2f3e437492ecc841bc9852ab46ce0e218e4723 Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Sun, 7 Jun 2020 23:41:54 +0200 Subject: Add basic support for checking idle status --- cfgdir.c | 10 ++ idle.c | 453 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ idle.h | 10 ++ main.c | 2 +- meson.build | 10 +- server.c | 21 +++ server.h | 7 + 7 files changed, 511 insertions(+), 2 deletions(-) create mode 100644 idle.c create mode 100644 idle.h 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[] = { @@ -48,6 +49,10 @@ struct cfg_key_value_map scfg_key_map[] = { .key_name = "remote", .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, @@ -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 +#include +#include +#include +#include +#include +#include + +#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; } @@ -308,6 +318,16 @@ server_add_local(struct cfg *cfg, struct server *scfg, struct sockaddr_in46 *add return true; } +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) { @@ -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, -- cgit v1.2.3