summaryrefslogtreecommitdiff
path: root/minecproxy/server.c
diff options
context:
space:
mode:
authorDavid Härdeman <david@hardeman.nu>2020-06-23 20:56:22 +0200
committerDavid Härdeman <david@hardeman.nu>2020-06-23 20:56:22 +0200
commitea053d96f7e89e053d4af8d39b04c5428760345f (patch)
tree8182ca73675ad3933b0f38cb48a99c69101309b4 /minecproxy/server.c
parent8c27290245b7bcc7cd2f72f3b4a7562294b43bbe (diff)
Big renaming, move some more functionality to shared lib
Diffstat (limited to 'minecproxy/server.c')
-rw-r--r--minecproxy/server.c836
1 files changed, 836 insertions, 0 deletions
diff --git a/minecproxy/server.c b/minecproxy/server.c
new file mode 100644
index 0000000..534ceca
--- /dev/null
+++ b/minecproxy/server.c
@@ -0,0 +1,836 @@
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sched.h>
+#include <poll.h>
+#include <errno.h>
+#include <sched.h>
+
+#include "main.h"
+#include "uring.h"
+#include "ptimer.h"
+#include "server.h"
+#include "server-proxy.h"
+#include "server-rcon.h"
+#include "config-parser.h"
+#include "idle.h"
+#include "systemd.h"
+
+static bool
+set_property(struct server *server, char **property, const char *value)
+{
+ assert_return(server && !*property && !empty_str(value), false);
+
+ *property = xstrdup(value);
+ if (!*property) {
+ error("strdup: %m");
+ return false;
+ }
+
+ return true;
+}
+
+void
+server_refdump(struct server *server)
+{
+ struct server_local *local;
+ struct server_proxy *proxy;
+
+ assert_return(server);
+
+ uring_task_refdump(&server->task);
+ uring_task_refdump(&server->exec_task);
+ uring_task_refdump(&server->ann_task);
+ uring_task_refdump(&server->idle_task);
+ list_for_each_entry(local, &server->locals, list)
+ local_refdump(local);
+ list_for_each_entry(proxy, &server->proxys, list)
+ proxy_refdump(proxy);
+ rcon_refdump(server);
+}
+
+static void
+server_free(struct uring_task *task)
+{
+ struct server *server = container_of(task, struct server, task);
+
+ assert_return(task);
+
+ debug(DBG_SRV, "freeing server %s (%p)", server->name, server);
+ list_del(&server->list);
+ xfree(server->pretty_name);
+ xfree(server->start_exec);
+ xfree(server->stop_exec);
+ xfree(server->systemd_service);
+ xfree(server->systemd_obj);
+ xfree(server->rcon_password);
+ xfree(server->name);
+ xfree(server);
+}
+
+void
+server_delete(struct server *server)
+{
+ struct server_local *local, *ltmp;
+ struct server_proxy *proxy, *ptmp;
+ struct saddr *remote;
+ struct saddr *rcon;
+ struct saddr *tmp;
+ struct dns_async *dns, *dtmp;
+
+ assert_return(server);
+
+ verbose("Removing server %s", server->name);
+ server->state = SERVER_STATE_DEAD;
+
+ rcon_delete(server);
+
+ list_for_each_entry_safe(local, ltmp, &server->locals, list)
+ local_delete(local);
+
+ list_for_each_entry_safe(proxy, ptmp, &server->proxys, list)
+ proxy_delete(proxy);
+
+ list_for_each_entry_safe(rcon, tmp, &server->rcons, list) {
+ list_del(&rcon->list);
+ xfree(rcon);
+ }
+
+ list_for_each_entry_safe(remote, tmp, &server->remotes, list) {
+ list_del(&remote->list);
+ xfree(remote);
+ }
+
+ list_for_each_entry_safe(dns, dtmp, &server->dnslookups, list)
+ gai_cancel(&dns->gcb);
+
+ uring_task_destroy(&server->idle_task);
+ uring_poll_cancel(&server->exec_task);
+ uring_task_put(&server->exec_task);
+ uring_task_destroy(&server->task);
+ uring_task_put(&server->ann_task);
+}
+
+void
+server_delete_by_name(const char *name)
+{
+ struct server *server;
+
+ assert_return(!empty_str(name));
+
+ list_for_each_entry(server, &cfg->servers, list) {
+ if (streq(server->name, name)) {
+ server_delete(server);
+ return;
+ }
+ }
+}
+
+static void
+server_dump(struct server *server)
+{
+ struct server_local *local;
+ struct saddr *remote;
+ struct saddr *rcon;
+
+ assert_return(server);
+
+ verbose("Server %s:", server->name);
+ switch (server->type) {
+ case SERVER_TYPE_ANNOUNCE:
+ verbose(" * Type: announce");
+ break;
+ case SERVER_TYPE_PROXY:
+ verbose(" * Type: proxy");
+ break;
+ default:
+ verbose(" * Type: unknown");
+ break;
+ }
+ verbose(" * Name: %s", server->pretty_name ? server->pretty_name : "<undefined>");
+ verbose(" * Announce port: %" PRIu16, server->announce_port);
+
+ if (!list_empty(&server->locals)) {
+ verbose(" * Local:");
+ list_for_each_entry(local, &server->locals, list)
+ verbose(" * %s", local->local.addrstr);
+ }
+
+ if (!list_empty(&server->remotes)) {
+ verbose(" * Remote:");
+ list_for_each_entry(remote, &server->remotes, list)
+ verbose(" * %s", remote->addrstr);
+ }
+
+ if (!list_empty(&server->rcons)) {
+ verbose(" * RCon:");
+ list_for_each_entry(rcon, &server->rcons, list)
+ verbose(" * %s", rcon->addrstr);
+ }
+
+ verbose("");
+}
+
+static void
+server_exec_free(struct uring_task *task)
+{
+ assert_return(task);
+
+ debug(DBG_SRV, "called");
+}
+
+#ifndef P_PIDFD
+#define P_PIDFD 3
+#endif
+
+/* FIXME: update states */
+static void
+server_exec_done(struct uring_task *task, int res)
+{
+ struct server *server = container_of(task, struct server, exec_task);
+ int r;
+ siginfo_t info;
+
+ assert_return(task);
+ assert_task_alive_or(DBG_SRV, task, goto out);
+ /* Should we leave child processes running? */
+
+ if (!(res & POLLIN)) {
+ error("unexpected result: %i", res);
+ goto out;
+ }
+
+ r = waitid(P_PIDFD, server->exec_task.fd, &info, WEXITED);
+ if (r < 0) {
+ error("waitid: %m");
+ goto out;
+ }
+
+ if (info.si_status == 0)
+ debug(DBG_SRV, "command successfully executed");
+ else
+ error("command failed: %i", info.si_status);
+
+out:
+ uring_task_close_fd(&server->exec_task);
+}
+
+static int
+server_exec_child(void *ptr)
+{
+ const char *cmd = ptr;
+
+ assert_return(ptr, EINVAL);
+
+ execl(cmd, cmd, NULL);
+ return errno;
+}
+
+#ifndef CLONE_PIDFD
+#define CLONE_PIDFD 0x00001000
+#endif
+
+static bool
+server_exec(struct server *server, const char *cmd)
+{
+ char stack[4096]; /* Beautiful/horrible hack :) */
+ int pidfd;
+ int r;
+
+ assert_return(server && cmd && server->exec_task.fd < 1, false);
+
+ r = clone(server_exec_child, stack + sizeof(stack),
+ CLONE_VM | CLONE_VFORK | CLONE_PIDFD | SIGCHLD,
+ (void *)cmd, &pidfd);
+ if (r < 0) {
+ error("clone: %m: %i", r);
+ return false;
+ }
+
+ uring_task_set_fd(&server->exec_task, pidfd);
+ uring_poll(&server->exec_task, POLLIN, server_exec_done);
+ return true;
+}
+
+static bool
+server_check_running(struct server *server)
+{
+ assert_return(server, false);
+
+ /* FIXME: other methods, rcon? */
+ if (server->systemd_service) {
+ verbose("%s: checking if systemd service is running", server->name);
+ if (systemd_service_running(server)) {
+ server->state = SERVER_STATE_RUNNING;
+ return true;
+ } else {
+ server->state = SERVER_STATE_STOPPED;
+ return false;
+ }
+ }
+
+ return false;
+}
+
+bool
+server_start(struct server *server)
+{
+ assert_return(server, false);
+ assert_task_alive_or(DBG_SRV, &server->task, return false);
+
+ switch (server->start_method) {
+
+ case SERVER_START_METHOD_EXEC:
+ verbose("Starting server %s via external cmd", server->name);
+ return server_exec(server, server->start_exec);
+
+ case SERVER_START_METHOD_SYSTEMD:
+ verbose("Starting server %s via systemd (%s)",
+ server->name, server->systemd_service);
+
+ if (systemd_service_start(server)) {
+ server->state = SERVER_STATE_RUNNING;
+ return true;
+ } else
+ return server_check_running(server);
+
+ case SERVER_START_METHOD_UNDEFINED:
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool
+server_stop(struct server *server)
+{
+ assert_return(server, false);
+ assert_task_alive_or(DBG_SRV, &server->task, return false);
+
+ switch (server->stop_method) {
+
+ case SERVER_STOP_METHOD_EXEC:
+ verbose("Stopping server %s via external cmd", server->name);
+ return server_exec(server, server->stop_exec);
+
+ case SERVER_STOP_METHOD_SYSTEMD:
+ verbose("Stopping server %s via systemd (%s)",
+ server->name, server->systemd_service);
+ if (systemd_service_stop(server)) {
+ server->state = SERVER_STATE_STOPPED;
+ return true;
+ } else
+ return server_check_running(server);
+
+ case SERVER_STOP_METHOD_RCON:
+ verbose("Stopping server %s via rcon", server->name);
+ rcon_stop(server);
+ return true;
+
+ case SERVER_STOP_METHOD_UNDEFINED:
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static void
+server_idle_free(struct uring_task *task)
+{
+ assert_return(task);
+
+ debug(DBG_ANN, "called");
+}
+
+void
+server_set_active_players(struct server *server, int count)
+{
+ assert_return(server);
+ assert_task_alive(DBG_IDLE, &server->idle_task);
+
+ debug(DBG_IDLE, "%s: currently %i active players",
+ server->name, count);
+
+ if (count < 0)
+ return;
+
+ server->state = SERVER_STATE_RUNNING;
+ if (count > 0)
+ server->idle_count = 0;
+ else if (count == 0)
+ server->idle_count++;
+
+ if (server->idle_count > server->idle_timeout) {
+ verbose("stopping idle server %s", server->name);
+ server_stop(server);
+ }
+}
+
+static void
+server_idle_connected_cb(struct connection *conn, bool connected)
+{
+ struct server *server = container_of(conn, struct server, idle_conn);
+
+ assert_return(conn);
+ assert_task_alive(DBG_IDLE, &server->idle_task);
+
+ if (!connected) {
+ debug(DBG_IDLE,
+ "idle check connection to remote server (%s) failed",
+ server->name);
+ server->idle_count = 0;
+ server->state = SERVER_STATE_STOPPED;
+ return;
+ }
+
+ debug(DBG_IDLE, "connected to remote %s\n", conn->remote.addrstr);
+ idle_check_get_player_count(server, conn);
+}
+
+bool
+server_idle_check(struct server *server)
+{
+ assert_return(server, false);
+
+ if (server->state == SERVER_STATE_INIT ||
+ server->state == SERVER_STATE_DEAD)
+ return false;
+
+ if (server->idle_timeout < 1)
+ return false;
+
+ if (list_empty(&server->remotes))
+ return false;
+
+ if (!list_empty(&server->proxys)) {
+ server->idle_count = 0;
+ return true;
+ }
+
+ connect_any(&server->idle_task, &server->remotes,
+ &server->idle_conn, server_idle_connected_cb);
+ return true;
+}
+
+static void
+server_announce_free(struct uring_task *task)
+{
+ assert_return(task);
+
+ debug(DBG_ANN, "called");
+}
+
+static void
+server_announce_cb(struct uring_task *task, int res)
+{
+ struct server *server = container_of(task, struct server, ann_task);
+
+ assert_return(task);
+
+ if (res < 0)
+ error("%s: failure %i", server->name, res);
+ else if (res == server->ann_buf.len)
+ debug(DBG_ANN, "%s: ok (%i)", server->name, res);
+ else
+ debug(DBG_ANN, "%s: unexpected result: %i", server->name, res);
+
+ uring_task_set_fd(&server->ann_task, -1);
+}
+
+bool
+server_announce(struct server *server, int fd)
+{
+ assert_return(server && fd >= 0, false);
+
+ if (server->state == SERVER_STATE_INIT ||
+ server->state == SERVER_STATE_DEAD)
+ return false;
+
+ debug(DBG_ANN, "announcing server: %s", server->name);
+ uring_task_set_fd(&server->ann_task, fd);
+ uring_tbuf_sendmsg(&server->ann_task, server_announce_cb);
+ return true;
+}
+
+bool
+server_commit(struct server *server)
+{
+ struct server_local *local;
+ uint16_t port;
+ int r;
+
+ assert_return(server && server->name, false);
+ assert_task_alive_or(DBG_SRV, &server->task, return false);
+
+ if (server->state != SERVER_STATE_INIT) {
+ error("called in wrong state");
+ return false;
+ }
+
+ if (!list_empty(&server->proxys)) {
+ error("%s: proxys not empty?", server->name);
+ return false;
+ }
+
+ if (!list_empty(&server->dnslookups)) {
+ debug(DBG_SRV, "called with pending DNS requests");
+ return true;
+ }
+
+ if (server->stop_method == SERVER_STOP_METHOD_RCON &&
+ list_empty(&server->rcons)) {
+ error("%s: rcon stop method missing rcon address",
+ server->name);
+ return false;
+ }
+
+ if (server->stop_method == SERVER_STOP_METHOD_RCON &&
+ !server->rcon_password) {
+ error("%s: rcon stop method missing rcon password",
+ server->name);
+ return false;
+ }
+
+ if ((server->start_method == SERVER_START_METHOD_SYSTEMD ||
+ server->stop_method == SERVER_STOP_METHOD_SYSTEMD) &&
+ !server->systemd_service) {
+ error("%s: systemd start/stop method missing systemd service",
+ server->name);
+ return false;
+ }
+
+ if (server->systemd_service && !server->systemd_obj) {
+ server->systemd_obj = systemd_object_path(server->systemd_service);
+ if (!server->systemd_obj) {
+ error("%s: failed to create systemd object path (%s)",
+ server->name, server->systemd_service);
+ return false;
+ }
+ }
+
+ if (server->idle_timeout > 0 &&
+ server->stop_method == SERVER_STOP_METHOD_UNDEFINED) {
+ error("%s: idle_timeout set but missing stop method", server->name);
+ return false;
+ }
+
+ switch (server->type) {
+ case SERVER_TYPE_ANNOUNCE:
+ if (server->announce_port < 1) {
+ error("%s: missing announce port", server->name);
+ return false;
+ }
+
+ if (server->start_method != SERVER_START_METHOD_UNDEFINED) {
+ error("%s: can't set start_method for announce server", server->name);
+ return false;
+ }
+
+ if (!list_empty(&server->locals)) {
+ error("%s: can't set local addresses for announce server", server->name);
+ return false;
+ }
+
+ if (!list_empty(&server->remotes)) {
+ error("%s: can't set remote addresses for announce server", server->name);
+ return false;
+ }
+
+ break;
+
+ case SERVER_TYPE_PROXY:
+ if (server->announce_port >= 1) {
+ error("%s: can't set announce port for proxy server", server->name);
+ return false;
+ }
+
+ if (list_empty(&server->locals)) {
+ error("%s: missing local addresses for proxy server", server->name);
+ return false;
+ }
+
+ if (list_empty(&server->remotes)) {
+ error("%s: missing remote addresses for proxy server", server->name);
+ return false;
+ }
+
+ list_for_each_entry(local, &server->locals, list) {
+ port = saddr_port(&local->local);
+
+ if (port == 0) {
+ error("%s: invalid local port", server->name);
+ return false;
+ }
+
+ if (server->announce_port < 1)
+ server->announce_port = port;
+
+ if (server->announce_port != port) {
+ error("%s: multiple local ports", server->name);
+ return false;
+ }
+ }
+
+ if (server->announce_port < 1) {
+ error("%s: can't determine which port to announce", server->name);
+ return false;
+ }
+
+ break;
+
+ default:
+ error("%s: can't determine server type", server->name);
+ return false;
+ }
+
+ if (!server->pretty_name) {
+ char *suffix;
+
+ suffix = strrchr(server->name, '.');
+ if (!suffix || suffix == server->name) {
+ error("invalid server name: %s", server->name);
+ return false;
+ }
+
+ server->pretty_name = xstrndup(server->name, suffix - server->name);
+ if (!server->pretty_name) {
+ error("failed to create display name: %s", server->name);
+ return false;
+ }
+ }
+
+ r = snprintf(server->ann_buf.buf, sizeof(server->ann_buf.buf),
+ "[MOTD]%s[/MOTD][AD]%" PRIu16 "[/AD]",
+ server->pretty_name, server->announce_port);
+ if (r < 1 || r >= sizeof(server->ann_buf.buf)) {
+ error("%s: unable to create announce msg: %i\n", server->name, r);
+ return false;
+ }
+ server->ann_buf.len = r;
+
+ /* FIXME: config, dont reread config if server running, make sure fd is available before this is called */
+ server_dump(server);
+
+ list_for_each_entry(local, &server->locals, list)
+ local_open(local);
+
+ server->state = SERVER_STATE_CFG_OK;
+
+ server_check_running(server);
+
+ debug(DBG_SRV, "success");
+ return true;
+}
+
+bool
+server_add_remote(struct server *server, struct saddr *remote)
+{
+ assert_return(server && remote, false);
+ assert_task_alive_or(DBG_SRV, &server->task, return false);
+
+ debug(DBG_SRV, "adding remote: %s", remote->addrstr);
+ list_add(&remote->list, &server->remotes);
+ return true;
+}
+
+bool
+server_add_local(struct server *server, struct saddr *saddr)
+{
+ struct server_local *local;
+
+ assert_return(server && saddr, false);
+ assert_task_alive_or(DBG_SRV, &server->task, return false);
+
+ local = local_new(server, saddr);
+ if (!local)
+ return false;
+
+ list_add(&local->list, &server->locals);
+ return true;
+}
+
+bool
+server_add_rcon(struct server *server, struct saddr *rcon)
+{
+ assert_return(server && rcon, false);
+ assert_task_alive_or(DBG_SRV, &server->task, return false);
+
+ debug(DBG_SRV, "adding rcon: %s", rcon->addrstr);
+ list_add(&rcon->list, &server->rcons);
+ return true;
+}
+
+bool
+server_set_rcon_password(struct server *server, const char *password)
+{
+ assert_return(server && !empty_str(password), false);
+
+ return set_property(server, &server->rcon_password, password);
+}
+
+bool
+server_set_systemd_service(struct server *server, const char *service)
+{
+ const char *suffix;
+ char *tmp;
+
+ assert_return(server && !empty_str(service) && !server->systemd_service, false);
+
+ suffix = strrchr(service, '.');
+ if (!suffix || !streq(suffix, ".service")) {
+ tmp = zmalloc(strlen(service) + strlen(".service") + 1);
+ if (tmp)
+ sprintf(tmp, "%s.service", service);
+ } else
+ tmp = xstrdup(service);
+
+ if (!tmp) {
+ error("malloc/strdup: %m");
+ return false;
+ }
+
+ server->systemd_service = tmp;
+ return true;
+}
+
+bool
+server_set_stop_method(struct server *server,
+ enum server_stop_method stop_method)
+{
+ assert_return(server->stop_method == SERVER_STOP_METHOD_UNDEFINED &&
+ stop_method != SERVER_STOP_METHOD_UNDEFINED, false);
+
+ server->stop_method = stop_method;
+ return true;
+}
+
+bool
+server_set_start_method(struct server *server,
+ enum server_start_method start_method)
+{
+ assert_return(server->start_method == SERVER_START_METHOD_UNDEFINED &&
+ start_method != SERVER_START_METHOD_UNDEFINED, false);
+
+ server->start_method = start_method;
+ return true;
+}
+
+bool
+server_set_stop_exec(struct server *server, const char *cmd)
+{
+ assert_return(server && !empty_str(cmd), false);
+
+ return set_property(server, &server->stop_exec, cmd);
+}
+
+bool
+server_set_start_exec(struct server *server, const char *cmd)
+{
+ assert_return(server && !empty_str(cmd), false);
+
+ return set_property(server, &server->start_exec, cmd);
+}
+
+bool
+server_set_idle_timeout(struct server *server, uint16_t timeout)
+{
+ assert_return(server && timeout > 0 && server->idle_timeout == 0, false);
+
+ server->idle_timeout = timeout;
+ return true;
+}
+
+bool
+server_set_port(struct server *server, uint16_t port)
+{
+ assert_return(server && port > 0 && server->announce_port == 0, false);
+
+ server->announce_port = htons(port);
+ return true;
+}
+
+bool
+server_set_type(struct server *server, enum server_type type)
+{
+ assert_return(server && type != SERVER_TYPE_UNDEFINED, false);
+
+ switch (type) {
+ case SERVER_TYPE_ANNOUNCE:
+ server->type = SERVER_TYPE_ANNOUNCE;
+ break;
+ case SERVER_TYPE_PROXY:
+ server->type = SERVER_TYPE_PROXY;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+bool
+server_set_pretty_name(struct server *server, const char *pretty_name)
+{
+ assert_return(server && !empty_str(pretty_name), false);
+
+ return set_property(server, &server->pretty_name, pretty_name);
+}
+
+struct server *
+server_new(const char *name)
+{
+ struct server *server;
+
+ assert_return(!empty_str(name), NULL);
+
+ list_for_each_entry(server, &cfg->servers, list) {
+ if (!streq(name, server->name))
+ continue;
+ error("attempt to add duplicate server: %s", name);
+ return server;
+ }
+
+ verbose("Adding server %s", name);
+ server = zmalloc(sizeof(*server));
+ if (!server) {
+ error("malloc: %m");
+ return NULL;
+ }
+
+ server->state = SERVER_STATE_INIT;
+ server->type = SERVER_TYPE_UNDEFINED;
+ server->name = xstrdup(name);
+ server->stop_method = SERVER_STOP_METHOD_UNDEFINED;
+ server->start_method = SERVER_START_METHOD_UNDEFINED;
+ server->idle_timeout = 0;
+
+ uring_task_init(&server->task, server->name, uring_parent(), server_free);
+ uring_task_set_buf(&server->task, &server->tbuf);
+
+ uring_task_init(&server->ann_task, "announce", &server->task, server_announce_free);
+ uring_task_set_buf(&server->ann_task, &server->ann_buf);
+ saddr_set_ipv4(&server->ann_task.saddr, cinet_addr(224,0,2,60), htons(4445));
+
+ uring_task_init(&server->exec_task, "exec", &server->task, server_exec_free);
+
+ uring_task_init(&server->idle_task, "idle", &server->task, server_idle_free);
+ uring_task_set_buf(&server->idle_task, &server->idle_buf);
+
+ rcon_init(server);
+
+ list_init(&server->remotes);
+ list_init(&server->locals);
+ list_init(&server->proxys);
+ list_init(&server->rcons);
+ list_init(&server->dnslookups);
+ list_add(&server->list, &cfg->servers);
+
+ return server;
+}