#include #include #include #include #include #include #include #include "main.h" #include "uring.h" #include "server.h" #include "idle.h" #include "ptimer.h" struct idle { struct ptimer_task ptask; struct uring_task task; }; static inline void write_byte(char **pos, char byte) { assert_return(pos && *pos); **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 #define MC_UNDEFINED_VERSION -1 static inline void write_varint(char **pos, int32_t orig) { assert_return(pos && *pos); 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; assert_return(pos && *pos && remain && res, -1); 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) { assert_return(pos && *pos && bytes && n > 0); memcpy(*pos, bytes, n); *pos += n; } static inline void write_str(char **pos, const char *str) { size_t len; assert_return(pos && *pos && !empty_str(str)); len = strlen(str); write_varint(pos, len); write_bytes(pos, str, len); } static inline void write_cmd(char **pos, const char *begin, const char *end) { assert_return(pos && *pos && begin && end && end > begin); write_varint(pos, end - begin); write_bytes(pos, begin, end - begin); } static int idle_check_handshake_complete(struct uring_task *task, _unused_ int res) { size_t remain; char *pos; int32_t mclen; int r; assert_return(task, -EINVAL); assert_task_alive_or(DBG_IDLE, task, return -EINTR); remain = task->tbuf->len; pos = task->tbuf->buf; r = read_varint(&pos, &remain, &mclen); if (r < 0) { error("failed to parse message length"); return -EINVAL; } else if (r == 0) { return 0; } else if (mclen < 2) { error("short MC message"); return -EINVAL; } if (mclen < remain) { debug(DBG_IDLE, "short MC message - len: %" PRIi32 ", remain: %zu", mclen, remain); return 0; } debug(DBG_IDLE, "Complete message"); return 1; } #define ONLINE_NEEDLE "\"online\"" static int get_player_count(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; assert_return(pos && remain > 0, -1); online = memmem(pos, remain, ONLINE_NEEDLE, STRLEN(ONLINE_NEEDLE)); if (!online) { error("could not find online count in JSON"); return -1; } remain -= (online - pos); end = memchr(online, '}', remain); if (!end) { error("could not parse JSON (no end)"); return -1; } *end = '\0'; if (sscanf(online, ONLINE_NEEDLE " : %u", &count) != 1) { error("could not parse JSON (online count)"); return -1; } return count; } static void idle_check_handshake_reply(struct uring_task *task, int res) { struct server *server = container_of(task, struct server, idle_task); int32_t mclen; int32_t jsonlen; char *pos; size_t remain; int player_count = -1; int r; assert_return(task); assert_task_alive(DBG_IDLE, task); debug(DBG_IDLE, "res: %i", res); if (res < 0) goto out; /* fprintf(stderr, "Received MC message (%i bytes):", res); for (int i = 0; i < res; i++) fprintf(stderr, "0x%02hhx ", idle->remotebuf[i]); fprintf(stderr, "n"); */ remain = server->idle_buf.len; pos = server->idle_buf.buf; r = read_varint(&pos, &remain, &mclen); if (r <= 0 || mclen < 2 || mclen < remain) { /* Should not happen since the msg has been checked already */ error("invalid message"); goto out; } debug(DBG_IDLE, "MC message - len: %" PRIi32 ", remain: %zu", mclen, remain); if (*pos != MC_STATUS_REPLY) { error("unknown server reply (0x%02hhx)", *pos); goto out; } pos++; remain--; r = read_varint(&pos, &remain, &jsonlen); if (r <= 0) { error("could not read JSON length"); goto out; } debug(DBG_IDLE, "MC - json len: %" PRIi32 ", remain: %zu", jsonlen, remain); if (jsonlen < remain) { error("invalid JSON length"); goto out; } /* fprintf(stderr, "JSON: "); for (int i = 0; i < jsonlen; i++) fprintf(stderr, "%c", pos[i]); */ player_count = get_player_count(pos, remain); out: uring_task_close_fd(task); server_set_active_players(server, player_count); return; } static void idle_check_handshake_sent(struct uring_task *task, int res) { assert_return(task); assert_task_alive(DBG_IDLE, task); debug(DBG_IDLE, "sent %i bytes", res); if (res < 0) { uring_task_close_fd(task); return; } uring_tbuf_read_until(task, idle_check_handshake_complete, idle_check_handshake_reply); } void idle_check_get_player_count(struct server *server, struct connection *conn) { char buf[1024]; char *pos; char *cmdbuf = server->idle_buf.buf; uint16_t port; char hostname[INET6_ADDRSTRLEN]; assert_return(server && conn && server->idle_task.priv); port = saddr_port(&conn->remote); saddr_addr(&conn->remote, hostname, sizeof(hostname)); pos = buf; write_byte(&pos, MC_HELO); write_varint(&pos, MC_UNDEFINED_VERSION); 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); server->idle_buf.len = (cmdbuf - server->idle_buf.buf); debug(DBG_IDLE, "sending MC message (%zu bytes)", server->idle_buf.len); uring_tbuf_write(&server->idle_task, idle_check_handshake_sent); } static void idle_cb(struct ptimer_task *ptask) { struct idle *idle = container_of(ptask, struct idle, ptask); struct server *server; assert_return(ptask); assert_task_alive(DBG_IDLE, &idle->task); debug(DBG_IDLE, "timer fired"); list_for_each_entry(server, &cfg->servers, list) server_idle_check(server); } static void idle_free(struct uring_task *task) { struct idle *idle = container_of(task, struct idle, task); assert_return(task); debug(DBG_IDLE, "task %p, idle %p", task, idle); xfree(idle); } void idle_refdump() { assert_return_silent(cfg->idle); uring_task_refdump(&cfg->idle->task); } void idle_delete() { assert_return(cfg->idle); debug(DBG_IDLE, "closing fd %i", cfg->idle->task.fd); ptimer_del_task(&cfg->idle->ptask); uring_task_destroy(&cfg->idle->task); cfg->idle = NULL; } void idle_init() { struct idle *idle; assert_return(!cfg->idle); idle = zmalloc(sizeof(*idle)); if (!idle) die("malloc: %m"); ptask_init(&idle->ptask, 60, 0, idle_cb); uring_task_init(&idle->task, "idle", uring_parent(), idle_free); ptimer_add_task(&idle->ptask); cfg->idle = idle; }