/* SPDX-License-Identifier: GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include "minecproxy.h" #include "uring.h" #include "ptimer.h" #include "server.h" #include "server-config.h" #include "server-proxy.h" #include "server-rcon.h" #include "idle.h" #include "shared/config-parser.h" #include "shared/systemd.h" 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->listenings, 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 (%p)", server); list_del(&server->list); xfree(server); } void server_delete(struct server *server) { struct server_local *local, *ltmp; struct server_proxy *proxy, *ptmp; assert_return(server); verbose("Removing server %s", server->scfg.name); server->state = SERVER_STATE_DEAD; rcon_delete(server); list_for_each_entry_safe(local, ltmp, &server->listenings, list) local_delete(local); list_for_each_entry_safe(proxy, ptmp, &server->proxys, list) proxy_delete(proxy); 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); scfg_delete(&server->scfg); } void server_delete_by_filename(const char *filename) { struct server *server; assert_return(!empty_str(filename)); list_for_each_entry(server, &cfg->servers, list) { if (streq(server->scfg.filename, filename)) { server_delete(server); return; } } } /* FIXME: Share with minecctl */ static void server_dump(struct server *server) { struct server_local *listen; struct saddr *local; struct saddr *remote; struct saddr *rcon; assert_return(server); verbose("Server %s:", server->scfg.name); verbose(" * Filename: %s", server->scfg.filename); switch (server->scfg.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->scfg.pretty_name ? server->scfg.pretty_name : ""); verbose(" * Announce port: %" PRIu16, server->scfg.announce_port); if (!list_empty(&server->listenings)) { verbose(" * Listening:"); list_for_each_entry(listen, &server->listenings, list) verbose(" * %s", listen->local.addrstr); } if (!list_empty(&server->scfg.locals)) { verbose(" * Locals:"); list_for_each_entry(local, &server->scfg.locals, list) verbose(" * %s", local->addrstr); } if (!list_empty(&server->scfg.remotes)) { verbose(" * Remote:"); list_for_each_entry(remote, &server->scfg.remotes, list) verbose(" * %s", remote->addrstr); } if (!list_empty(&server->scfg.rcons)) { verbose(" * RCon:"); list_for_each_entry(rcon, &server->scfg.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) { const char *error; assert_return(server, false); /* FIXME: other methods, rcon? */ if (server->scfg.systemd_service) { verbose("%s: checking if systemd service is running", server->scfg.name); if (systemd_service_running(&server->scfg, &error)) { 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->scfg.start_method) { case SERVER_START_METHOD_EXEC: verbose("Starting server %s via external cmd", server->scfg.name); return server_exec(server, server->scfg.start_exec); case SERVER_START_METHOD_SYSTEMD: verbose("Starting server %s via systemd (%s)", server->scfg.name, server->scfg.systemd_service); if (systemd_service_start(&server->scfg)) { 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->scfg.stop_method) { case SERVER_STOP_METHOD_EXEC: verbose("Stopping server %s via external cmd", server->scfg.name); return server_exec(server, server->scfg.stop_exec); case SERVER_STOP_METHOD_SYSTEMD: verbose("Stopping server %s via systemd (%s)", server->scfg.name, server->scfg.systemd_service); if (systemd_service_stop(&server->scfg)) { 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->scfg.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_update_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->scfg.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->scfg.idle_timeout) { verbose("stopping idle server %s", server->scfg.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->scfg.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->scfg.idle_timeout < 1) return false; if (list_empty(&server->scfg.remotes)) return false; if (!list_empty(&server->proxys)) { server->idle_count = 0; return true; } connect_any(&server->idle_task, &server->scfg.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->scfg.name, res); else if (res == server->ann_buf.len) debug(DBG_ANN, "%s: ok (%i)", server->scfg.name, res); else debug(DBG_ANN, "%s: unexpected result: %i", server->scfg.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 || server->scfg.announce == SERVER_ANNOUNCE_NEVER || (server->scfg.announce == SERVER_ANNOUNCE_WHEN_RUNNING && server->state != SERVER_STATE_RUNNING)) return false; debug(DBG_ANN, "announcing server: %s", server->scfg.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) { const char *error; struct saddr *saddr, *tmp; int r; assert_return(server && server->scfg.name, false); assert_task_alive_or(DBG_SRV, &server->task, return false); if (!list_empty(&server->scfg.dnslookups)) { debug(DBG_SRV, "%s: called with pending DNS requests", server->scfg.name); 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->scfg.name); return false; } if (!scfg_validate(&server->scfg, &error)) { error("%s: failed to validate config file (%s)", server->scfg.name, error); server_delete(server); return false; } r = snprintf(server->ann_buf.buf, sizeof(server->ann_buf.buf), "[MOTD]%s[/MOTD][AD]%" PRIu16 "[/AD]", server->scfg.pretty_name ? server->scfg.pretty_name : server->scfg.name, server->scfg.announce_port); if (r < 1 || r >= sizeof(server->ann_buf.buf)) { error("%s: unable to create announce msg: %i\n", server->scfg.name, r); return false; } server->ann_buf.len = r; /* FIXME: check interactions with inotify */ server_dump(server); list_for_each_entry_safe(saddr, tmp, &server->scfg.locals, list) { struct server_local *local; list_del(&saddr->list); local = local_new(server, saddr); if (!local) { error("%s: failed to create local listener", server->scfg.name); server_delete(server); return false; } list_add(&local->list, &server->listenings); if (!local_open(local)) { error("%s: failed to open listening port", server->scfg.name); server_delete(server); return false; } } server->state = SERVER_STATE_CFG_OK; server_check_running(server); debug(DBG_SRV, "%s: success", server->scfg.name); return true; } struct server *server_new(const char *filename) { struct server *server; assert_return(!empty_str(filename), NULL); list_for_each_entry(server, &cfg->servers, list) { if (!streq(filename, server->scfg.filename)) continue; error("attempt to add duplicate server: %s", filename); return server; } verbose("Adding server %s", filename); server = zmalloc(sizeof(*server)); if (!server) { error("malloc: %m"); return NULL; } if (!scfg_init(&server->scfg, filename)) { xfree(server); return NULL; } server->state = SERVER_STATE_INIT; uring_task_init(&server->task, server->scfg.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); INIT_LIST_HEAD(&server->listenings); INIT_LIST_HEAD(&server->proxys); list_add(&server->list, &cfg->servers); return server; }