summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Härdeman <david@hardeman.nu>2020-06-27 15:18:45 +0200
committerDavid Härdeman <david@hardeman.nu>2020-06-27 15:18:45 +0200
commit99b2c70137fef05a5a18f439b9010ddba455f5cb (patch)
treeb0c08cfce14019cd634e6b4b84d0cf0f6e8eee6a
parenta87e894ba3f3a8915389f651fb034f0d1835630c (diff)
Create a shared mc protocol implementation and use it in the proxy and cmd line tool
-rw-r--r--minecctl/mc-commands.c77
-rw-r--r--minecctl/mc-commands.h6
-rw-r--r--minecctl/meson.build1
-rw-r--r--minecctl/misc-commands.c15
-rw-r--r--minecctl/misc-commands.h2
-rw-r--r--minecctl/misc.c9
-rw-r--r--minecctl/rcon-commands.c19
-rw-r--r--minecctl/rcon-commands.h2
-rw-r--r--minecproxy/idle.c258
-rw-r--r--shared/mc-protocol.c282
-rw-r--r--shared/mc-protocol.h13
-rw-r--r--shared/meson.build1
12 files changed, 433 insertions, 252 deletions
diff --git a/minecctl/mc-commands.c b/minecctl/mc-commands.c
new file mode 100644
index 0000000..0ac20d0
--- /dev/null
+++ b/minecctl/mc-commands.c
@@ -0,0 +1,77 @@
+#include <unistd.h>
+
+#include "utils.h"
+#include "minecctl.h"
+#include "server.h"
+#include "mc-commands.h"
+#include "misc.h"
+#include "mc-protocol.h"
+
+bool
+do_mc_pcount(struct cfg *cfg, unsigned *online, unsigned *max)
+{
+ struct server *server;
+ struct saddr *saddr;
+ char buf[4096];
+ size_t plen, off;
+ ssize_t r;
+ bool rv = false;
+ int fd;
+
+ server = server_get_default(cfg);
+
+ fd = connect_any(&server->mc_addrs, true);
+ if (fd < 0) {
+ error("%s: unable to connect", server->name);
+ return false;
+ }
+
+ /* FIXME: connect_any needs to indicate the address it used */
+ saddr = list_first_entry(&server->mc_addrs, struct saddr, list);
+ if (!saddr) {
+ error("No saddr");
+ goto out;
+ }
+
+ if (!mc_protocol_create_status_request(buf, sizeof(buf), &plen, saddr)) {
+ error("Failed to create req");
+ goto out;
+ }
+
+ /* FIXME: do proper checks for EINTR etc */
+ off = 0;
+ while (off < plen) {
+ r = write(fd, buf + off, plen - off);
+ if (r <= 0) {
+ error("write failed: %zi (%m)", r);
+ goto out;
+ }
+ off += r;
+ }
+
+ off = 0;
+ while (off < sizeof(buf)) {
+ r = read(fd, buf + off, sizeof(buf) - off);
+ if (r <= 0) {
+ error("Read failed %zi: %m", r);
+ goto out;
+ }
+
+ off += r;
+
+ if (mc_is_handshake_complete(buf, off)) {
+ rv = true;
+ break;
+ }
+ }
+
+ if (!mc_protocol_parse_status_reply(buf, off, online, max)) {
+ error("Failed to get player count");
+ return false;
+ }
+
+out:
+ close(fd);
+ return rv;
+}
+
diff --git a/minecctl/mc-commands.h b/minecctl/mc-commands.h
new file mode 100644
index 0000000..3140a73
--- /dev/null
+++ b/minecctl/mc-commands.h
@@ -0,0 +1,6 @@
+#ifndef foomccommandshfoo
+#define foomccomanndshfoo
+
+bool do_mc_pcount(struct cfg *cfg, unsigned *online, unsigned *max);
+
+#endif
diff --git a/minecctl/meson.build b/minecctl/meson.build
index e3bddcc..7a41203 100644
--- a/minecctl/meson.build
+++ b/minecctl/meson.build
@@ -2,6 +2,7 @@ minecctl_sources = [
'minecctl.c',
'server.c',
'rcon-commands.c',
+ 'mc-commands.c',
'misc-commands.c',
'misc.c',
]
diff --git a/minecctl/misc-commands.c b/minecctl/misc-commands.c
index 6b70c55..c64a005 100644
--- a/minecctl/misc-commands.c
+++ b/minecctl/misc-commands.c
@@ -2,6 +2,8 @@
#include "minecctl.h"
#include "server.h"
#include "misc-commands.h"
+#include "rcon-commands.h"
+#include "mc-commands.h"
bool
do_list(struct cfg *cfg)
@@ -16,3 +18,16 @@ do_list(struct cfg *cfg)
return true;
}
+bool
+do_pcount(struct cfg *cfg)
+{
+ unsigned x, y;
+
+ if (do_rcon_pcount(cfg, &y, &x))
+ error("Rcon says %u/%u", y, x);
+
+ if (do_mc_pcount(cfg, &y, &x))
+ error("MC says %u/%u", y, x);
+
+ return true;
+}
diff --git a/minecctl/misc-commands.h b/minecctl/misc-commands.h
index e0dc675..8270601 100644
--- a/minecctl/misc-commands.h
+++ b/minecctl/misc-commands.h
@@ -3,5 +3,7 @@
bool do_list(struct cfg *cfg);
+bool do_pcount(struct cfg *cfg);
+
#endif
diff --git a/minecctl/misc.c b/minecctl/misc.c
index bb33161..72eb03c 100644
--- a/minecctl/misc.c
+++ b/minecctl/misc.c
@@ -68,8 +68,13 @@ connect_any(struct list_head *addrs, bool may_fail)
bool connected = false;
int sfd;
- if (list_empty(addrs))
- die("No address to connect to");
+ /* FIXME: check callers and coordinate debug msg */
+ if (list_empty(addrs)) {
+ if (may_fail)
+ return -1;
+ else
+ die("No address to connect to");
+ }
list_for_each_entry(saddr, addrs, list) {
verbose("Attempting connection to %s", saddr->addrstr);
diff --git a/minecctl/rcon-commands.c b/minecctl/rcon-commands.c
index 02b970f..cf43c5e 100644
--- a/minecctl/rcon-commands.c
+++ b/minecctl/rcon-commands.c
@@ -387,23 +387,22 @@ do_stop_all(struct cfg *cfg) {
}
bool
-do_pcount(struct cfg *cfg) {
- int fd;
- unsigned current, max;
+do_rcon_pcount(struct cfg *cfg, unsigned *online, unsigned *max)
+{
struct server *server;
+ bool rv;
+ int fd;
server = server_get_default(cfg);
fd = rcon_login(cfg, server);
if (fd < 0)
return false;
- if (get_player_count(fd, &current, &max)) {
- info("Players: %u/%u", current, max);
- return true;
- } else {
- die("Failed to get player count");
- return false;
- }
+ rv = get_player_count(fd, online, max);
+
+ close(fd);
+
+ return rv;
}
bool
diff --git a/minecctl/rcon-commands.h b/minecctl/rcon-commands.h
index 5366bf9..1714dd5 100644
--- a/minecctl/rcon-commands.h
+++ b/minecctl/rcon-commands.h
@@ -9,7 +9,7 @@ bool do_stop(struct cfg *cfg);
bool do_stop_all(struct cfg *cfg);
-bool do_pcount(struct cfg *cfg);
+bool do_rcon_pcount(struct cfg *cfg, unsigned *online, unsigned *max);
bool do_console(struct cfg *cfg);
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);
diff --git a/shared/mc-protocol.c b/shared/mc-protocol.c
new file mode 100644
index 0000000..afc9b38
--- /dev/null
+++ b/shared/mc-protocol.c
@@ -0,0 +1,282 @@
+#include <stdint.h>
+#include <inttypes.h>
+#include <strings.h>
+
+#include "utils.h"
+
+#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 unsigned
+write_byte(char **pos, char byte)
+{
+ if (pos && *pos) {
+ **pos = byte;
+ (*pos)++;
+ }
+ return 1;
+}
+static inline unsigned
+write_varint(char **pos, int32_t v)
+{
+ uint32_t u = (uint32_t)v;
+ unsigned rv = 0;
+
+ do {
+ char x;
+
+ x = u & 0x7f;
+ if (u >>= 7)
+ x |= 0x80;
+ if (pos && *pos)
+ *((*pos)++) = x;
+ rv++;
+ } while (u);
+
+ return rv;
+}
+
+static inline unsigned
+write_bytes(char **pos, const char *bytes, size_t n)
+{
+ if (pos && *pos) {
+ memcpy(*pos, bytes, n);
+ *pos += n;
+ }
+
+ return n;
+}
+
+static inline unsigned
+write_str(char **pos, const char *str, size_t len)
+{
+ unsigned rv;
+
+ rv = write_varint(pos, len);
+ rv += write_bytes(pos, str, len);
+
+ return rv;
+}
+
+/*
+ * return value:
+ * > 0 = varint parsed
+ * 0 = need more bytes
+ * < 0 = error
+ */
+static inline int
+read_varint(const char **from, size_t *remain, int32_t *res)
+{
+ unsigned consumed;
+ uint32_t val = 0;
+
+ assert_return(from && *from && remain && res, -1);
+
+ for (consumed = 1; consumed <= *remain; consumed++) {
+ uint32_t tmp;
+
+ tmp = **from & 0x7f;
+ val += (tmp << (7 * (consumed - 1)));
+ (*from)++;
+
+ 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;
+}
+
+/*
+ * return value:
+ * > 0 = varint parsed
+ * 0 = need more bytes
+ * < 0 = error
+ */
+int
+mc_is_handshake_complete(const char *buf, size_t len)
+{
+ int32_t mclen;
+ int r;
+
+ assert_return(buf && len > 0, -1);
+
+ r = read_varint(&buf, &len, &mclen);
+ if (r < 0) {
+ error("failed to parse message length");
+ return -1;
+ } else if (r == 0) {
+ return 0;
+ } else if (mclen < 2) {
+ error("short MC message");
+ return -1;
+ }
+
+ if (mclen < len) {
+ debug(DBG_IDLE, "short MC message - len: %" PRIi32 ", remain: %zu",
+ mclen, len);
+ return 0;
+ }
+
+ debug(DBG_IDLE, "Complete message");
+ return 1;
+}
+
+#define PLAYERS_NEEDLE "\"players\""
+#define MAX_NEEDLE "\"max\""
+#define ONLINE_NEEDLE "\"online\""
+static bool
+mc_get_player_count(const char *buf, size_t remain,
+ unsigned *ronline, unsigned *rmax)
+{
+ /*
+ * Example JSON (line breaks added):
+ * {"description":{
+ * "text":"A Minecraft Server"},
+ * "players":{"max":20,"online":0},
+ * "version":{"name":"1.15.2","protocol":578}
+ * }
+ */
+ char *players;
+ char *pmax;
+ char *ponline;
+ unsigned online, max;
+
+ assert_return(buf && remain > 0, false);
+
+ players = memmem(buf, remain, PLAYERS_NEEDLE, STRLEN(PLAYERS_NEEDLE));
+ if (!players)
+ goto error;
+
+ remain -= (players - buf);
+
+ pmax = memmem(players, remain, MAX_NEEDLE, STRLEN(MAX_NEEDLE));
+ ponline = memmem(players, remain, ONLINE_NEEDLE, STRLEN(ONLINE_NEEDLE));
+ if (!pmax || !ponline)
+ goto error;
+
+ if ((sscanf(pmax, MAX_NEEDLE " : %u", &max) != 1) ||
+ (sscanf(ponline, ONLINE_NEEDLE " : %u", &online) != 1))
+ goto error;
+
+ if (ronline)
+ *ronline = online;
+
+ if (rmax)
+ *rmax = max;
+
+ return true;
+
+error:
+ error("could not parse server status reply");
+ return false;
+}
+
+bool
+mc_protocol_parse_status_reply(const char *buf, size_t len,
+ unsigned *online, unsigned *max)
+{
+ const char *from = buf;
+ size_t remain = len;
+ int32_t mclen;
+ int32_t jsonlen;
+ int r;
+
+ assert_return(buf && len > 0, -1);
+
+ r = read_varint(&from, &remain, &mclen);
+ if (r <= 0 || mclen < 2 || mclen < remain) {
+ /* Should not happen since the msg has been checked already */
+ error("invalid message");
+ return false;
+ }
+
+ debug(DBG_IDLE, "MC message - len: %" PRIi32 ", remain: %zu",
+ mclen, remain);
+
+ if (*from != MC_STATUS_REPLY) {
+ error("unknown server reply (0x%02hhx)", *from);
+ return false;
+ }
+
+ from++;
+ remain--;
+
+ r = read_varint(&from, &remain, &jsonlen);
+ if (r <= 0) {
+ error("could not read JSON length");
+ return false;
+ }
+
+ debug(DBG_IDLE, "MC - json len: %" PRIi32 ", remain: %zu",
+ jsonlen, remain);
+
+ if (jsonlen < remain) {
+ error("invalid JSON length");
+ return false;
+ }
+
+ if (mc_get_player_count(from, remain, online, max))
+ return true;
+
+ return false;
+}
+
+bool
+mc_protocol_create_status_request(char *buf, size_t len, size_t *rlen,
+ struct saddr *saddr)
+{
+ uint16_t port;
+ char hostname[INET6_ADDRSTRLEN];
+ size_t bodylen, hostlen, packetlen;
+ char *to = buf;
+
+ assert_return(buf && len > 0 && rlen && saddr, false);
+
+ port = saddr_port(saddr);
+ saddr_addr(saddr, hostname, sizeof(hostname));
+ hostlen = strlen(hostname);
+
+ /* First, a handshake, calculate body length... */
+ bodylen = write_byte(NULL, MC_HELO);
+ bodylen += write_varint(NULL, MC_UNDEFINED_VERSION);
+ bodylen += write_str(NULL, hostname, hostlen);
+ bodylen += write_byte(NULL, (port >> 8) & 0xff);
+ bodylen += write_byte(NULL, (port >> 0) & 0xff);
+ bodylen += write_byte(NULL, MC_NEXT_STATE_STATUS);
+
+ /* ...check buffer size... */
+ if (len < (MC_VARINT_MAX_BYTES + bodylen + 2 /* = status req */))
+ return false;
+
+ /* ...write header... */
+ packetlen = write_varint(&to, bodylen);
+
+ /* ...write body... */
+ packetlen += write_byte(&to, MC_HELO);
+ packetlen += write_varint(&to, MC_UNDEFINED_VERSION);
+ packetlen += write_str(&to, hostname, hostlen);
+ packetlen += write_byte(&to, (port >> 8) & 0xff);
+ packetlen += write_byte(&to, (port >> 0) & 0xff);
+ packetlen += write_byte(&to, MC_NEXT_STATE_STATUS);
+
+ /* ...then a status request, again with a header... */
+ packetlen += write_varint(&to, 1);
+
+ /* ...and body */
+ packetlen += write_byte(&to, MC_GET_STATUS);
+
+ *rlen = packetlen;
+ return true;
+}
+
diff --git a/shared/mc-protocol.h b/shared/mc-protocol.h
new file mode 100644
index 0000000..8ecc02a
--- /dev/null
+++ b/shared/mc-protocol.h
@@ -0,0 +1,13 @@
+#ifndef foomcprotocolhfoo
+#define foomcprotocolhfoo
+
+int mc_is_handshake_complete(const char *buf, size_t len);
+
+bool mc_protocol_parse_status_reply(const char *buf, size_t len,
+ unsigned *online, unsigned *max);
+
+bool mc_protocol_create_status_request(char *buf, size_t len, size_t *rlen,
+ struct saddr *saddr);
+
+#endif
+
diff --git a/shared/meson.build b/shared/meson.build
index ccfad4a..5b15c05 100644
--- a/shared/meson.build
+++ b/shared/meson.build
@@ -1,5 +1,6 @@
srcs_libshared = [
'rcon-protocol.c',
+ 'mc-protocol.c',
'config-parser.c',
'utils.c',
]