From 09f1abfe18258807c412bce88ad459984add0cd3 Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Tue, 9 Jun 2020 20:51:44 +0200 Subject: Add an rcon shutdown method --- cfgdir.c | 30 ++++- meson.build | 1 + rcon.c | 425 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ rcon.h | 10 ++ server.c | 38 +++++- server.h | 16 ++- 6 files changed, 512 insertions(+), 8 deletions(-) create mode 100644 rcon.c create mode 100644 rcon.h diff --git a/cfgdir.c b/cfgdir.c index f529cbe..f961011 100644 --- a/cfgdir.c +++ b/cfgdir.c @@ -30,10 +30,10 @@ enum scfg_keys { SCFG_KEY_START_METHOD, SCFG_KEY_STOP_EXEC, SCFG_KEY_START_EXEC, - /* - SCFG_KEY_SYSTEMD_SERVICE, SCFG_KEY_RCON, SCFG_KEY_RCON_PASSWORD, + /* + SCFG_KEY_SYSTEMD_SERVICE, */ }; @@ -78,6 +78,14 @@ struct cfg_key_value_map scfg_key_map[] = { .key_name = "start_exec", .key_value = SCFG_KEY_START_EXEC, .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "rcon", + .key_value = SCFG_KEY_RCON, + .value_type = CFG_VAL_TYPE_ADDRS, + }, { + .key_name = "rcon_password", + .key_value = SCFG_KEY_RCON_PASSWORD, + .value_type = CFG_VAL_TYPE_STRING, }, { .key_name = NULL, .key_value = SCFG_KEY_INVALID, @@ -155,6 +163,9 @@ scfg_parse(struct cfg *cfg, struct server *scfg) if (!strcmp(value.str, "exec")) { if (server_set_stop_method(cfg, scfg, SERVER_STOP_METHOD_EXEC)) break; + } else if (!strcmp(value.str, "rcon")) { + if (server_set_stop_method(cfg, scfg, SERVER_STOP_METHOD_RCON)) + break; } return; @@ -175,6 +186,21 @@ scfg_parse(struct cfg *cfg, struct server *scfg) return; break; + case SCFG_KEY_RCON: { + struct sockaddr_in46 *addr, *tmp; + + list_for_each_entry_safe(addr, tmp, &value.addr_list, list) { + list_del(&addr->list); + server_add_rcon(cfg, scfg, addr); + } + break; + } + + case SCFG_KEY_RCON_PASSWORD: + if (!server_set_rcon_password(cfg, scfg, value.str)) + return; + break; + case SCFG_KEY_INVALID: default: break; diff --git a/meson.build b/meson.build index 85dbab9..7a89f88 100644 --- a/meson.build +++ b/meson.build @@ -11,6 +11,7 @@ executable('mcproxy', 'announce.c', 'cfgdir.c', 'config.c', + 'rcon.c', 'idle.c', 'utils.c'], dependencies: uring) diff --git a/rcon.c b/rcon.c new file mode 100644 index 0000000..756d480 --- /dev/null +++ b/rcon.c @@ -0,0 +1,425 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "uring.h" +#include "config.h" +#include "server.h" +#include "rcon.h" + +struct rcon { + struct server *server; + struct uring_task task; + unsigned next_rcon; + struct sockaddr_in46 rcon; + char rconstr[ADDRSTRLEN]; + char rconbuf[4096]; + size_t rconbuflen; + size_t rconbufdone; +}; + +static void +rcon_free(struct uring_task *task) +{ + struct rcon *rcon = container_of(task, struct rcon, task); + + fprintf(stderr, "%s: called with task 0x%p and idle 0x%p\n", __func__, task, rcon); + rcon->server->rcon = NULL; + free(rcon); +} + +void +rcon_refdump(struct rcon *rcon) +{ + if (!rcon) + return; + + uring_task_refdump(&rcon->task); +} + +void +rcon_delete(struct cfg *cfg, struct server *server) +{ + struct rcon *rcon = server->rcon; + + if (!rcon) + return; + + fprintf(stderr, "%s called, closing fd %i\n", __func__, rcon->task.fd); + uring_cancel(cfg, &rcon->task); + uring_task_put(cfg, &rcon->task); + server->rcon = NULL; +} + +static int32_t +read_int(char **pos, size_t *len) +{ + uint32_t val; + char *p = *pos; + + if (len && *len < 4) + return 0; + + val = ((uint8_t)p[0] << 0); + val += ((uint8_t)p[1] << 8); + val += ((uint8_t)p[2] << 16); + val += ((uint8_t)p[3] << 24); + + *pos += 4; + if (len) + *len -= 4; + + return (int32_t)val; +} + +static void +write_int(char **pos, size_t *len, int32_t orig) +{ + uint32_t val = (uint32_t)orig; + char *p = *pos; + + p[0] = (val >> 0) & 0xff; + p[1] = (val >> 8) & 0xff; + p[2] = (val >> 16) & 0xff; + p[3] = (val >> 24) & 0xff; + *pos += 4; + if (len) + *len += 4; +} + +static void +write_str(char **pos, size_t *len, const char *str) +{ + size_t towrite = strlen(str); + + memcpy(*pos, str, towrite); + *pos += towrite; + if (len) + *len += towrite; +} + +static void +write_end(char **pos, size_t *len) +{ + char *p = *pos; + + p[0] = 0x00; + p[1] = 0x00; + *pos += 2; + if (len) + *len += 2; +} + +enum rcon_packet_type { + RCON_PACKET_LOGIN = 3, + RCON_PACKET_LOGIN_OK = 2, + RCON_PACKET_LOGIN_FAIL = -1, + RCON_PACKET_COMMAND = 2, + RCON_PACKET_RESPONSE = 0, +}; + +static void +create_packet(struct cfg *cfg, struct rcon *rcon, int32_t reqid, enum rcon_packet_type type, const char *msg) +{ + char *pos = &rcon->rconbuf[4]; + + rcon->rconbufdone = 0; + rcon->rconbuflen = 4; + write_int(&pos, &rcon->rconbuflen, reqid); + write_int(&pos, &rcon->rconbuflen, type); + write_str(&pos, &rcon->rconbuflen, msg); + write_end(&pos, &rcon->rconbuflen); + pos = &rcon->rconbuf[0]; + write_int(&pos, NULL, rcon->rconbuflen - 4); + + fprintf(stderr, "Created packet (reqid: %" PRIi32 ", type %" PRIi32 ", len %zu, payload: %s)\n", + reqid, type, rcon->rconbuflen, msg); +} + +static bool +packet_complete(struct cfg *cfg, struct rcon *rcon) +{ + char *pos = rcon->rconbuf; + size_t len = rcon->rconbufdone; + int32_t plen; + + if (rcon->rconbufdone < 14) + return false; + + plen = read_int(&pos, &len); + fprintf(stderr, "Reply is %zu bytes, packet size %" PRIi32 "\n", rcon->rconbufdone, plen + 4); + if (rcon->rconbufdone < plen + 4) + return false; + else + return true; +} + +static bool +read_packet(struct cfg *cfg, struct rcon *rcon, int32_t *id, int32_t *type, char **rmsg) +{ + char *pos = rcon->rconbuf; + size_t len = rcon->rconbufdone; + int32_t plen; + + plen = read_int(&pos, &len); + *id = read_int(&pos, &len); + *type = read_int(&pos, &len); + *rmsg = NULL; + + if (plen < 10) { + fprintf(stderr, "Invalid packet length: %" PRIi32 "\n", plen); + return false; + } + + fprintf(stderr, "Remaining = %zu\n", len); + if (len > 2) { + char *msg; + + msg = malloc(len - 1); + if (!msg) { + perror("malloc"); + return false; + } + + memcpy(msg, pos, len - 2); + msg[len - 2] = '\0'; + + *rmsg = msg; + pos += len - 2; + len = 2; + } + + if (len < 2) { + fprintf(stderr, "Short message\n"); + return false; + } + + if (pos[0] != '\0' || pos[1] != '\0') { + fprintf(stderr, "Invalid trailer\n"); + return false; + } + + fprintf(stderr, "Response - len: %" PRIi32 ", id: %" PRIi32 + ", type: %" PRIi32 ", msg: %s\n", + plen, *id, *type, *rmsg); + + return true; +} + +static void +rcon_stop_reply(struct cfg *cfg, struct uring_task *task, int res) +{ + struct rcon *rcon = container_of(task, struct rcon, task); + int32_t id; + int32_t type; + char *msg; + + fprintf(stderr, "%s: result %i\n", __func__, res); + if (res < 0) + goto out; + + rcon->rconbufdone += res; + + /* FIXME: could be multiple packets */ + if (packet_complete(cfg, rcon)) { + fprintf(stderr, "Packet complete\n"); + read_packet(cfg, rcon, &id, &type, &msg); + if (id != 2) { + fprintf(stderr, "RCon stop cmd failed - unexpected reply id (%" PRIi32 ")\n", id); + goto out; + } else if (type != RCON_PACKET_RESPONSE) { + fprintf(stderr, "RCon stop cmd failed - unexpected reply type (%" PRIi32 ")\n", type); + goto out; + } + fprintf(stderr, "RCon stop successful (%s)\n", msg); + free(msg); + + } else { + fprintf(stderr, "Packet not complete\n"); + uring_read(cfg, &rcon->task, &rcon->rconbuf + rcon->rconbufdone, + sizeof(rcon->rconbuf) - rcon->rconbufdone, 0, rcon_stop_reply); + } + +out: + uring_task_put(cfg, &rcon->task); +} + +static void +rcon_stop_sent(struct cfg *cfg, struct uring_task *task, int res) +{ + struct rcon *rcon = container_of(task, struct rcon, task); + + fprintf(stderr, "%s: result %i\n", __func__, res); + if (res < 0) { + uring_task_put(cfg, &rcon->task); + return; + } + + rcon->rconbufdone += res; + if (rcon->rconbufdone < rcon->rconbuflen) { + uring_write(cfg, &rcon->task, rcon->rconbuf + rcon->rconbufdone, + rcon->rconbuflen - rcon->rconbufdone, rcon_stop_sent); + return; + } + + fprintf(stderr, "%s: stop cmd sent\n", __func__); + rcon->rconbufdone = 0; + rcon->rconbuflen = 0; + uring_read(cfg, &rcon->task, &rcon->rconbuf, sizeof(rcon->rconbuf), 0, rcon_stop_reply); +} + +static void +rcon_login_reply(struct cfg *cfg, struct uring_task *task, int res) +{ + struct rcon *rcon = container_of(task, struct rcon, task); + int32_t id; + int32_t type; + char *msg; + + fprintf(stderr, "%s: result %i\n", __func__, res); + if (res < 0) + goto out; + + rcon->rconbufdone += res; + + if (packet_complete(cfg, rcon)) { + fprintf(stderr, "Packet complete\n"); + read_packet(cfg, rcon, &id, &type, &msg); + if (id != 1) { + fprintf(stderr, "RCon login failed - unexpected reply id (%" PRIi32 ")\n", id); + goto out; + } else if (type == RCON_PACKET_LOGIN_FAIL) { + fprintf(stderr, "RCon login failed - incorrect password\n"); + goto out; + } else if (type != RCON_PACKET_LOGIN_OK) { + fprintf(stderr, "RCon login failed - unexpected reply type (%" PRIi32 ")\n", type); + goto out; + } + free(msg); + fprintf(stderr, "RCon login successful\n"); + create_packet(cfg, rcon, 2, RCON_PACKET_COMMAND, "stop"); + uring_write(cfg, &rcon->task, rcon->rconbuf, rcon->rconbuflen, rcon_stop_sent); + + } else { + fprintf(stderr, "Packet not complete\n"); + uring_read(cfg, &rcon->task, &rcon->rconbuf + rcon->rconbufdone, + sizeof(rcon->rconbuf) - rcon->rconbufdone, 0, rcon_login_reply); + } + + return; + +out: + uring_task_put(cfg, &rcon->task); +} + +static void +rcon_login_sent(struct cfg *cfg, struct uring_task *task, int res) +{ + struct rcon *rcon = container_of(task, struct rcon, task); + + fprintf(stderr, "%s: result %i\n", __func__, res); + if (res < 0) { + uring_task_put(cfg, &rcon->task); + return; + } + + rcon->rconbufdone += res; + if (rcon->rconbufdone < rcon->rconbuflen) { + uring_write(cfg, &rcon->task, rcon->rconbuf + rcon->rconbufdone, + rcon->rconbuflen - rcon->rconbufdone, rcon_login_sent); + return; + } + + fprintf(stderr, "%s: login sent\n", __func__); + rcon->rconbufdone = 0; + rcon->rconbuflen = 0; + uring_read(cfg, &rcon->task, &rcon->rconbuf, sizeof(rcon->rconbuf), 0, rcon_login_reply); +} + +static void rcon_connect_next_rcon(struct cfg *cfg, struct rcon *rcon); + +static void +rcon_connected(struct cfg *cfg, struct uring_task *task, int res) +{ + struct rcon *rcon = container_of(task, struct rcon, task); + + fprintf(stderr, "%s: connected %i\n", __func__, res); + if (res < 0) { + rcon_connect_next_rcon(cfg, rcon); + return; + } + + create_packet(cfg, rcon, 1, RCON_PACKET_LOGIN, rcon->server->rcon_password); + uring_write(cfg, &rcon->task, rcon->rconbuf, rcon->rconbuflen, rcon_login_sent); +} + +/* FIXME: Parts of this could be shared with proxy.c, probably in server.c */ +static void +rcon_connect_next_rcon(struct cfg *cfg, struct rcon *rcon) +{ + struct sockaddr_in46 *remote, *tmp; + struct server *scfg = rcon->server; + int sfd; + unsigned i = 0; + +again: + remote = NULL; + list_for_each_entry(tmp, &scfg->rcons, list) { + if (i == rcon->next_rcon) { + remote = tmp; + break; + } + i++; + } + + if (!remote) { + fprintf(stderr, "No more rcon addresses to attempt\n"); + uring_task_put(cfg, &rcon->task); + return; + } + + rcon->next_rcon++; + rcon->rcon = *remote; + sockaddr_to_str(&rcon->rcon, rcon->rconstr, sizeof(rcon->rconstr)); + fprintf(stderr, "%s: attempting rcon connection to %s (len %u)\n", + scfg->name, rcon->rconstr, rcon->rcon.addrlen); + + sfd = socket(rcon->rcon.storage.ss_family, SOCK_STREAM, 0); + if (sfd < 0) { + perror("socket"); + goto again; + } + + uring_task_set_fd(&rcon->task, sfd); + uring_connect(cfg, &rcon->task, &rcon->rcon, rcon_connected); +} + +void +rcon_init(struct cfg *cfg, struct server *server) +{ + struct rcon *rcon; + + if (!server) + return; + + if (list_empty(&server->rcons) || !server->rcon_password) + return; + + rcon = zmalloc(sizeof(*rcon)); + if (!rcon) + perrordie("malloc"); + + uring_task_init(&rcon->task, "rcon", &server->task, rcon_free); + rcon->server = server; + server->rcon = rcon; + + rcon_connect_next_rcon(cfg, rcon); +} diff --git a/rcon.h b/rcon.h new file mode 100644 index 0000000..8b410e1 --- /dev/null +++ b/rcon.h @@ -0,0 +1,10 @@ +#ifndef foorconhfoo +#define foorconhfoo + +void rcon_refdump(struct rcon *rcon); + +void rcon_delete(struct cfg *cfg, struct server *server); + +void rcon_init(struct cfg *cfg, struct server *server); + +#endif diff --git a/server.c b/server.c index 5cac0a6..c3bb433 100644 --- a/server.c +++ b/server.c @@ -13,6 +13,7 @@ #include "proxy.h" #include "utils.h" #include "idle.h" +#include "rcon.h" struct server_local { struct sockaddr_in46 addr; @@ -75,6 +76,7 @@ server_delete(struct cfg *cfg, struct server *scfg) fprintf(stderr, "Removing server cfg: %s\n", scfg->name); idle_delete(cfg, scfg); + rcon_delete(cfg, scfg); list_for_each_entry_safe(remote, rtmp, &scfg->remotes, list) { list_del(&remote->list); @@ -125,12 +127,15 @@ server_dump(struct server *scfg) } fprintf(stderr, " * Pretty name: %s\n", scfg->pretty_name ? scfg->pretty_name : ""); fprintf(stderr, " * Announce port: %" PRIu16 "\n", scfg->announce_port); - fprintf(stderr, " * Listening ports:\n"); + fprintf(stderr, " * Listening:\n"); list_for_each_entry(local, &scfg->locals, list) fprintf(stderr, " * %s\n", local->addrstr); - fprintf(stderr, " * Remote ports:\n"); + fprintf(stderr, " * Remote:\n"); list_for_each_entry(remote, &scfg->remotes, list) fprintf(stderr, " * %s\n", sockaddr_to_str(remote, abuf, sizeof(abuf))); + fprintf(stderr, " * RCon:\n"); + list_for_each_entry(remote, &scfg->rcons, list) + fprintf(stderr, " * %s\n", sockaddr_to_str(remote, abuf, sizeof(abuf))); } static void @@ -271,7 +276,6 @@ server_start(struct cfg *cfg, struct server *scfg) switch (scfg->start_method) { case SERVER_START_METHOD_EXEC: - return exec_cmd(cfg, scfg, scfg->start_exec); case SERVER_START_METHOD_UNDEFINED: @@ -291,9 +295,12 @@ server_stop(struct cfg *cfg, struct server *scfg) switch (scfg->stop_method) { case SERVER_STOP_METHOD_EXEC: - return exec_cmd(cfg, scfg, scfg->stop_exec); + case SERVER_STOP_METHOD_RCON: + rcon_init(cfg, scfg); + return true; + case SERVER_STOP_METHOD_UNDEFINED: default: break; @@ -316,6 +323,10 @@ server_commit(struct cfg *cfg, struct server *scfg) /* FIXME: running? */ + if (scfg->stop_method == SERVER_STOP_METHOD_RCON && + (list_empty(&scfg->rcons) || !scfg->rcon_password)) + return false; + switch (scfg->type) { case SERVER_TYPE_ANNOUNCE: if (scfg->announce_port < 1) @@ -422,6 +433,24 @@ server_add_local(struct cfg *cfg, struct server *scfg, struct sockaddr_in46 *add return true; } +bool +server_add_rcon(struct cfg *cfg, struct server *scfg, + struct sockaddr_in46 *addr) +{ + if (!scfg || !addr) + return false; + + list_add(&addr->list, &scfg->rcons); + return true; +} + +bool +server_set_rcon_password(struct cfg *cfg, struct server *scfg, + const char *password) +{ + return set_property(cfg, scfg, &scfg->rcon_password, password); +} + bool server_set_stop_method(struct cfg *cfg, struct server *scfg, enum server_stop_method stop_method) @@ -532,6 +561,7 @@ server_new(struct cfg *cfg, const char *name) list_init(&scfg->remotes); list_init(&scfg->locals); list_init(&scfg->proxys); + list_init(&scfg->rcons); memset(&scfg->mcast_iov, 0, sizeof(scfg->mcast_iov)); scfg->mcast_iov.iov_base = scfg->mcast_buf; memset(&scfg->mcast_msg, 0, sizeof(scfg->mcast_msg)); diff --git a/server.h b/server.h index cb4fd76..43c5b9e 100644 --- a/server.h +++ b/server.h @@ -9,6 +9,7 @@ enum server_type { enum server_stop_method { SERVER_STOP_METHOD_UNDEFINED, + SERVER_STOP_METHOD_RCON, SERVER_STOP_METHOD_EXEC }; @@ -25,6 +26,7 @@ struct server { struct list_head locals; struct list_head remotes; struct list_head proxys; + struct list_head rcons; bool running; enum server_stop_method stop_method; @@ -36,6 +38,10 @@ struct server { char buf[4096]; size_t len; + /* For rcon connections */ + struct rcon *rcon; + char *rcon_password; + /* For announce messages */ struct iovec mcast_iov; struct msghdr mcast_msg; @@ -63,10 +69,16 @@ bool server_stop(struct cfg *cfg, struct server *scfg); bool server_commit(struct cfg *cfg, struct server *scfg); bool server_add_remote(struct cfg *cfg, struct server *scfg, - struct sockaddr_in46 *remote); + struct sockaddr_in46 *addr); bool server_add_local(struct cfg *cfg, struct server *scfg, - struct sockaddr_in46 *local); + struct sockaddr_in46 *addr); + +bool server_add_rcon(struct cfg *cfg, struct server *scfg, + struct sockaddr_in46 *addr); + +bool server_set_rcon_password(struct cfg *cfg, struct server *scfg, + const char *password); bool server_set_stop_method(struct cfg *cfg, struct server *scfg, enum server_stop_method stop_method); -- cgit v1.2.3