From 99b2c70137fef05a5a18f439b9010ddba455f5cb Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Sat, 27 Jun 2020 15:18:45 +0200 Subject: Create a shared mc protocol implementation and use it in the proxy and cmd line tool --- minecproxy/idle.c | 258 ++++-------------------------------------------------- 1 file changed, 19 insertions(+), 239 deletions(-) (limited to 'minecproxy/idle.c') diff --git a/minecproxy/idle.c b/minecproxy/idle.c index 8fcb934..70d8099 100644 --- a/minecproxy/idle.c +++ b/minecproxy/idle.c @@ -11,195 +11,32 @@ #include "server.h" #include "idle.h" #include "ptimer.h" +#include "mc-protocol.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; + r = mc_is_handshake_complete(task->tbuf->buf, task->tbuf->len); + debug(DBG_IDLE, "mc_is_handshake_complete returned %i", r); + return r; } 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; + unsigned online, max; int player_count = -1; - int r; assert_return(task); assert_task_alive(DBG_IDLE, task); @@ -208,55 +45,12 @@ idle_check_handshake_reply(struct uring_task *task, int 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); + if (mc_protocol_parse_status_reply(server->idle_buf.buf, + server->idle_buf.len, + &online, &max)) + player_count = online; + else + error("failed to parse reply"); out: uring_task_close_fd(task); @@ -284,31 +78,17 @@ idle_check_handshake_sent(struct uring_task *task, int res) 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); + if (!mc_protocol_create_status_request(server->idle_buf.buf, + sizeof(server->idle_buf.buf), + &server->idle_buf.len, + &conn->remote)) { + error("failed to create mc request"); + /* FIXME: is this enough? */ + return; + } - 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); -- cgit v1.2.3