From ea053d96f7e89e053d4af8d39b04c5428760345f Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Tue, 23 Jun 2020 20:56:22 +0200 Subject: Big renaming, move some more functionality to shared lib --- minecproxy/server.c | 836 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 836 insertions(+) create mode 100644 minecproxy/server.c (limited to 'minecproxy/server.c') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 : ""); + 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; +} -- cgit v1.2.3