summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Härdeman <david@hardeman.nu>2020-06-09 20:51:44 +0200
committerDavid Härdeman <david@hardeman.nu>2020-06-09 20:51:44 +0200
commit09f1abfe18258807c412bce88ad459984add0cd3 (patch)
treeccbd07eabfb4c1a08de912d2dd0d09678cca090f
parent5fd0055aee5124b9ad6573e5ed363d5bf63bcf80 (diff)
Add an rcon shutdown method
-rw-r--r--cfgdir.c30
-rw-r--r--meson.build1
-rw-r--r--rcon.c425
-rw-r--r--rcon.h10
-rw-r--r--server.c38
-rw-r--r--server.h16
6 files changed, 512 insertions, 8 deletions
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,
*/
};
@@ -79,6 +79,14 @@ struct cfg_key_value_map scfg_key_map[] = {
.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,
.value_type = CFG_VAL_TYPE_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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#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 : "<undefined>");
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)
@@ -423,6 +434,24 @@ server_add_local(struct cfg *cfg, struct server *scfg, struct sockaddr_in46 *add
}
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);