#define _GNU_SOURCE #include #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]; struct uring_task_buf tbuf; }; 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 = varint parsed * zero = need more bytes * 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 0; else if (consumed > MC_VARINT_MAX_BYTES) return -1; *remain -= consumed; *res = (int32_t)val; return 1; } 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); } static int idle_check_handshake_complete(struct cfg *cfg, struct uring_task *task, int res) { size_t remain; char *pos; int32_t mclen; int r; remain = task->tbuf->len; pos = task->tbuf->buf; r = read_varint(&pos, &remain, &mclen); if (r < 0) { fprintf(stderr, "Failed to parse message length\n"); return -EINVAL; } else if (r == 0) { return 0; } else if (mclen < 2) { fprintf(stderr, "Short MC message\n"); return -EINVAL; } fprintf(stderr, "MC message len: %" PRIi32 "\n", mclen); fprintf(stderr, "Remain: %zu\n", remain); if (mclen < remain) return 0; fprintf(stderr, "Complete message\n"); return 1; } #define ONLINE_NEEDLE "\"online\"" static int get_player_count(struct cfg *cfg, const char *pos, size_t remain) { /* * 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, ONLINE_NEEDLE, strlen(ONLINE_NEEDLE)); if (!online) { fprintf(stderr, "Could not find online count in JSON\n"); return -1; } remain -= (online - pos); end = memchr(online, '}', remain); if (!end) { fprintf(stderr, "Could not parse JSON (no end)\n"); return -1; } *end = '\0'; if (sscanf(online, ONLINE_NEEDLE " : %u", &count) != 1) { fprintf(stderr, "Could not parse JSON (online count)\n"); return -1; } return count; } 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 player_count; int r; fprintf(stderr, "%s: received %i bytes\n", __func__, res); if (res < 0) goto error; /* 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->tbuf.len; pos = idle->tbuf.buf; r = read_varint(&pos, &remain, &mclen); if (r <= 0 || mclen < 2 || mclen < remain) { /* Should not happen since the msg has been checked already */ fprintf(stderr, "Invalid message\n"); goto error; } fprintf(stderr, "%s: MC message len: %" PRIi32 "\n", __func__, mclen); fprintf(stderr, "%s: Remain: %zu\n", __func__, remain); 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"); player_count = get_player_count(cfg, pos, remain); fprintf(stderr, "We have %i players\n", player_count); if (player_count < 0) goto error; else if (player_count > 0) idle->server->idle_count = 0; else { idle->server->idle_count++; if (idle->server->idle_count > idle->server->idle_timeout) { fprintf(stderr, "Stopping idle server %s\n", idle->server->name); server_stop(cfg, idle->server); } } return; error: uring_task_close_fd(cfg, task); 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) { uring_task_close_fd(cfg, task); return; } uring_tbuf_read_until(cfg, &idle->idlecheck, idle_check_handshake_complete, 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->tbuf.buf; uint16_t port = 25565; /* FIXME: Write real remote addr and port */ fprintf(stderr, "%s: connected %i\n", __func__, res); if (res < 0) { uring_task_close_fd(cfg, task); 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->tbuf.len = (cmdbuf - idle->tbuf.buf); fprintf(stderr, "Sending MC message (%zu bytes):\n", idle->tbuf.len); for (pos = idle->tbuf.buf; pos < cmdbuf; pos++) fprintf(stderr, "0x%02hhx ", *pos); fprintf(stderr, "\n"); uring_tbuf_write(cfg, &idle->idlecheck, 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__); 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), 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; xfree(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_task_destroy(cfg, &idle->idlecheck); uring_task_destroy(cfg, &idle->task); 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_set_fd(&idle->task, ifd); uring_task_init(&idle->idlecheck, "idlecheck", &idle->task, idle_check_free); uring_task_set_buf(&idle->idlecheck, &idle->tbuf); idle->server = server; server->idle = idle; uring_read(cfg, &idle->task, &idle->value, sizeof(idle->value), idle_cb); }