#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; }