#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "main.h" #include "uring.h" #include "server.h" #include "proxy.h" #include "utils.h" #include "idle.h" #include "rcon.h" #include "systemd.h" struct server_local { struct saddr local; struct saddr client; struct uring_task task; struct list_head list; }; static bool set_property(struct cfg *cfg, struct server *scfg, char **property, const char *value) { if (!cfg || !scfg || empty_str(value) || *property) return 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; uring_task_refdump(&server->task); list_for_each_entry(local, &server->locals, list) uring_task_refdump(&local->task); list_for_each_entry(proxy, &server->proxys, list) proxy_refdump(proxy); if (server->idle) idle_refdump(server->idle); } static void server_free(struct uring_task *task) { struct server *scfg = container_of(task, struct server, task); debug(DBG_SRV, "freeing server %s (%p)\n", scfg->name, scfg); list_del(&scfg->list); xfree(scfg->pretty_name); xfree(scfg->start_exec); xfree(scfg->stop_exec); xfree(scfg->systemd_service); xfree(scfg->systemd_obj); xfree(scfg->rcon_password); xfree(scfg->name); xfree(scfg); } void server_delete(struct cfg *cfg, struct server *scfg) { struct server_local *local, *ltmp; struct server_proxy *proxy, *ptmp; struct saddr *remote; struct saddr *rcon; struct saddr *tmp; verbose("Removing server %s\n", scfg->name); idle_delete(cfg, scfg); rcon_delete(cfg, scfg); list_for_each_entry_safe(local, ltmp, &scfg->locals, list) uring_task_destroy(cfg, &local->task); list_for_each_entry_safe(proxy, ptmp, &scfg->proxys, list) proxy_delete(cfg, proxy); list_for_each_entry_safe(rcon, tmp, &scfg->rcons, list) { list_del(&rcon->list); xfree(rcon); } list_for_each_entry_safe(remote, tmp, &scfg->remotes, list) { list_del(&remote->list); xfree(remote); } uring_poll_cancel(cfg, &scfg->exec_task); uring_task_put(cfg, &scfg->exec_task); uring_task_destroy(cfg, &scfg->task); } void server_delete_by_name(struct cfg *cfg, const char *name) { struct server *scfg; if (!cfg || empty_str(name)) return; list_for_each_entry(scfg, &cfg->servers, list) { if (!strcmp(scfg->name, name)) { server_delete(cfg, scfg); return; } } } static void server_dump(struct server *scfg) { struct server_local *local; struct saddr *remote; struct saddr *rcon; verbose("Server %s:\n", scfg->name); switch (scfg->type) { case SERVER_TYPE_ANNOUNCE: verbose(" * Type: announce\n"); break; case SERVER_TYPE_PROXY: verbose(" * Type: proxy\n"); break; default: verbose(" * Type: unknown\n"); break; } verbose(" * Name: %s\n", scfg->pretty_name ? scfg->pretty_name : ""); verbose(" * Announce port: %" PRIu16 "\n", scfg->announce_port); if (!list_empty(&scfg->locals)) { verbose(" * Local:\n"); list_for_each_entry(local, &scfg->locals, list) verbose(" * %s\n", local->local.addrstr); } if (!list_empty(&scfg->remotes)) { verbose(" * Remote:\n"); list_for_each_entry(remote, &scfg->remotes, list) verbose(" * %s\n", remote->addrstr); } if (!list_empty(&scfg->rcons)) { verbose(" * RCon:\n"); list_for_each_entry(rcon, &scfg->rcons, list) verbose(" * %s\n", rcon->addrstr); } verbose("\n"); } static void server_local_free(struct uring_task *task) { struct server_local *local = container_of(task, struct server_local, task); debug(DBG_SRV, "task %p, local %p\n", task, local); list_del(&local->list); xfree(local); } static void server_local_accept(struct cfg *cfg, struct uring_task *task, int res) { struct server_local *local = container_of(task, struct server_local, task); struct server *scfg = container_of(task->parent, struct server, task); struct server_proxy *proxy; debug(DBG_SRV, "task %p, res %i, scfg %s\n", task, res, scfg->name); if (task->dead) { debug(DBG_SRV, "task dead\n"); return; } if (res < 0) { error("result was %i\n", res); goto out; } saddr_set_addrstr(&local->client); verbose("%s: incoming proxy connection: %s -> %s\n", scfg->name, local->client.addrstr, local->local.addrstr); if (list_empty(&scfg->remotes)) { /* This shouldn't be possible, checked before opening local */ error("scfg->remotes empty!\n"); uring_close(cfg, &local->task, res); goto out; } proxy = proxy_new(cfg, scfg, &local->client, res); if (!proxy) uring_close(cfg, &local->task, res); out: uring_accept(cfg, &local->task, &local->client, server_local_accept); } static bool server_local_open(struct cfg *cfg, struct server *scfg, struct server_local *local) { int sfd; int option; int r; sfd = socket(local->local.storage.ss_family, SOCK_STREAM | SOCK_CLOEXEC, 0); if (sfd < 0) { error("socket: %m"); goto out; } option = true; if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)) < 0) { error("setsockopt: %m"); goto out; } /* The MC protocol expects the client to send data first */ /* FIXME: could make this configurable */ option = true; if (setsockopt(sfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &option, sizeof(option)) < 0) error("setsockopt: %m"); /* FIXME: could make this configurable */ option = true; if (setsockopt(sfd, IPPROTO_IP, IP_FREEBIND, &option, sizeof(option)) < 0) error("setsockopt: %m"); socket_set_low_latency(cfg, sfd); r = bind(sfd, (struct sockaddr *)&local->local.storage, local->local.addrlen); if (r < 0) { error("bind: %m"); goto out; } r = listen(sfd, 100); if (r < 0) { error("listen: %m"); goto out; } uring_task_set_fd(&local->task, sfd); uring_accept(cfg, &local->task, &local->client, server_local_accept); return true; out: if (sfd >= 0) close(sfd); return false; } static void server_exec_free(struct uring_task *task) { //struct server *scfg = container_of(task, struct server, exec_task); debug(DBG_SRV, "called\n"); } #ifndef P_PIDFD #define P_PIDFD 3 #endif static void server_exec_done(struct cfg *cfg, struct uring_task *task, int res) { struct server *scfg = container_of(task, struct server, exec_task); int r; siginfo_t info; if (task->dead) { /* Should we leave child processes running? */ debug(DBG_SRV, "task dead\n"); goto out; } if (!(res & POLLIN)) { error("unexpected result: %i\n", res); goto out; } r = waitid(P_PIDFD, scfg->exec_task.fd, &info, WEXITED); if (r < 0) { error("waitid: %m"); goto out; } if (info.si_status == 0) debug(DBG_SRV, "command successfully executed\n"); else error("command failed: %i\n", info.si_status); out: uring_task_close_fd(cfg, &scfg->exec_task); } static int server_exec_child(void *ptr) { const char *cmd = ptr; execl(cmd, cmd, NULL); return errno; } #ifndef CLONE_PIDFD #define CLONE_PIDFD 0x00001000 #endif static bool server_exec(struct cfg *cfg, struct server *scfg, const char *cmd) { char stack[4096]; /* Beautiful/horrible hack :) */ int pidfd; int r; if (!cfg || !scfg || !cmd) return false; if (scfg->exec_task.fd >= 0) return 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"); return false; } uring_task_set_fd(&scfg->exec_task, pidfd); uring_poll(cfg, &scfg->exec_task, POLLIN, server_exec_done); return true; } bool server_start(struct cfg *cfg, struct server *scfg) { if (!cfg || !scfg) return false; switch (scfg->start_method) { case SERVER_START_METHOD_EXEC: verbose("Starting server %s via external cmd\n", scfg->name); return server_exec(cfg, scfg, scfg->start_exec); case SERVER_START_METHOD_SYSTEMD: verbose("Starting server %s via systemd (%s)\n", scfg->name, scfg->systemd_service); return systemd_service_start(cfg, scfg); case SERVER_START_METHOD_UNDEFINED: default: break; } return false; } bool server_stop(struct cfg *cfg, struct server *scfg) { if (!cfg || !scfg) return false; switch (scfg->stop_method) { case SERVER_STOP_METHOD_EXEC: verbose("Stopping server %s via external cmd\n", scfg->name); return server_exec(cfg, scfg, scfg->stop_exec); case SERVER_STOP_METHOD_SYSTEMD: verbose("Stopping server %s via systemd (%s)\n", scfg->name, scfg->systemd_service); return systemd_service_stop(cfg, scfg); case SERVER_STOP_METHOD_RCON: verbose("Stopping server %s via rcon\n", scfg->name); rcon_init(cfg, scfg); return true; case SERVER_STOP_METHOD_UNDEFINED: default: break; } return false; } bool server_commit(struct cfg *cfg, struct server *scfg) { struct server_local *local; uint16_t port; if (!scfg || !scfg->name) { error("called with invalid parameters\n"); return false; } if (!list_empty(&scfg->proxys)) { error("%s: proxys not empty?\n", scfg->name); return false; } /* FIXME: running? */ if (scfg->stop_method == SERVER_STOP_METHOD_RCON && (list_empty(&scfg->rcons) || !scfg->rcon_password)) { error("%s: rcon stop method but missing rcon password\n", scfg->name); return false; } if ((scfg->start_method == SERVER_START_METHOD_SYSTEMD || scfg->stop_method == SERVER_STOP_METHOD_SYSTEMD) && !scfg->systemd_service) { error("%s: systemd start/stop method but missing systemd service\n", scfg->name); return false; } if (scfg->systemd_service && !scfg->systemd_obj) { scfg->systemd_obj = systemd_service_object_path(cfg, scfg->systemd_service); if (!scfg->systemd_obj) { error("%s: failed to create systemd object path (%s)\n", scfg->name, scfg->systemd_service); return false; } } if (scfg->idle_timeout > 0 && scfg->stop_method == SERVER_STOP_METHOD_UNDEFINED) { error("%s: idle_timeout set but missing stop method\n", scfg->name); return false; } switch (scfg->type) { case SERVER_TYPE_ANNOUNCE: if (scfg->announce_port < 1) { error("%s: missing announce port\n", scfg->name); return false; } if (scfg->start_method != SERVER_START_METHOD_UNDEFINED) { error("%s: can't set start_method for announce server\n", scfg->name); return false; } if (!list_empty(&scfg->locals)) { error("%s: can't set local addresses for announce server\n", scfg->name); return false; } if (!list_empty(&scfg->remotes)) { error("%s: can't set remote addresses for announce server\n", scfg->name); return false; } break; case SERVER_TYPE_PROXY: if (scfg->announce_port >= 1) { error("%s: can't set announce port for proxy server\n", scfg->name); return false; } if (list_empty(&scfg->locals)) { error("%s: missing local addresses for proxy server\n", scfg->name); return false; } if (list_empty(&scfg->remotes)) { error("%s: missing remote addresses for proxy server\n", scfg->name); return false; } list_for_each_entry(local, &scfg->locals, list) { port = saddr_port(&local->local); if (port == 0) { error("%s: invalid local port\n", scfg->name); return false; } if (scfg->announce_port < 1) scfg->announce_port = port; if (scfg->announce_port != port) { error("%s: multiple local ports\n", scfg->name); return false; } } if (scfg->announce_port < 1) { error("%s: can't determine which port to announce\n", scfg->name); return false; } break; default: error("%s: can't determine server type\n", scfg->name); return false; } if (!scfg->pretty_name) { char *suffix; suffix = strrchr(scfg->name, '.'); if (!suffix || suffix == scfg->name) { error("%s: invalid server name\n", scfg->name); return false; } scfg->pretty_name = xstrndup(scfg->name, suffix - scfg->name); if (!scfg->pretty_name) { error("%s: failed to create display name\n", scfg->name); return false; } } /* FIXME: config, dont reread config if server running, make sure fd is available before this is called */ server_dump(scfg); list_for_each_entry(local, &scfg->locals, list) { server_local_open(cfg, scfg, local); } idle_init(cfg, scfg); if (scfg->systemd_service) { verbose("%s: checking if systemd service is running\n", scfg->name); systemd_service_running(cfg, scfg); } return true; } bool server_add_remote(struct cfg *cfg, struct server *scfg, struct saddr *remote) { if (!scfg || !remote) return false; list_add(&remote->list, &scfg->remotes); return true; } bool server_add_local(struct cfg *cfg, struct server *scfg, struct saddr *saddr) { struct server_local *local; if (!scfg || !saddr) { error("missing arguments\n"); return false; } local = zmalloc(sizeof(*local)); if (!local) { error("malloc: %m"); return false; } local->local = *saddr; uring_task_init(&local->task, "local", &scfg->task, server_local_free); list_add(&local->list, &scfg->locals); xfree(saddr); return true; } bool server_add_rcon(struct cfg *cfg, struct server *scfg, struct saddr *rcon) { if (!scfg || !rcon) return false; list_add(&rcon->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_systemd_service(struct cfg *cfg, struct server *scfg, const char *service) { const char *suffix; char *tmp; if (!cfg || !scfg || empty_str(service) || scfg->systemd_service) return false; suffix = strrchr(service, '.'); if (!suffix || strcmp(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; } scfg->systemd_service = tmp; return true; } bool server_set_stop_method(struct cfg *cfg, struct server *scfg, enum server_stop_method stop_method) { if (scfg->stop_method != SERVER_STOP_METHOD_UNDEFINED || stop_method == SERVER_STOP_METHOD_UNDEFINED) return false; scfg->stop_method = stop_method; return true; } bool server_set_start_method(struct cfg *cfg, struct server *scfg, enum server_start_method start_method) { if (scfg->start_method != SERVER_START_METHOD_UNDEFINED || start_method == SERVER_START_METHOD_UNDEFINED) return false; scfg->start_method = start_method; return true; } bool server_set_stop_exec(struct cfg *cfg, struct server *scfg, const char *cmd) { return set_property(cfg, scfg, &scfg->stop_exec, cmd); } bool server_set_start_exec(struct cfg *cfg, struct server *scfg, const char *cmd) { return set_property(cfg, scfg, &scfg->start_exec, cmd); } bool server_set_idle_timeout(struct cfg *cfg, struct server *scfg, uint16_t timeout) { if (!scfg || scfg->idle_timeout != 0) return false; scfg->idle_timeout = timeout; return true; } bool server_set_port(struct cfg *cfg, struct server *scfg, uint16_t port) { if (!scfg || scfg->announce_port != 0) return false; scfg->announce_port = htons(port); return true; } bool server_set_type(struct cfg *cfg, struct server *scfg, enum server_type type) { if (!scfg || scfg->type != SERVER_TYPE_UNDEFINED) return false; switch (type) { case SERVER_TYPE_ANNOUNCE: scfg->type = SERVER_TYPE_ANNOUNCE; break; case SERVER_TYPE_PROXY: scfg->type = SERVER_TYPE_PROXY; break; default: return false; } return true; } bool server_set_pretty_name(struct cfg *cfg, struct server *scfg, const char *pretty_name) { return set_property(cfg, scfg, &scfg->pretty_name, pretty_name); } struct server * server_new(struct cfg *cfg, const char *name) { struct server *scfg; list_for_each_entry(scfg, &cfg->servers, list) { if (strcmp(name, scfg->name)) continue; error("attempt to add duplicate server: %s\n", name); return scfg; } verbose("Adding server %s\n", name); scfg = zmalloc(sizeof(*scfg)); if (!scfg) { error("malloc"); return NULL; } scfg->type = SERVER_TYPE_UNDEFINED; scfg->name = xstrdup(name); scfg->running = false; scfg->stop_method = SERVER_STOP_METHOD_UNDEFINED; scfg->start_method = SERVER_START_METHOD_UNDEFINED; uring_task_init(&scfg->task, "scfg", uring_parent(cfg), server_free); uring_task_set_buf(&scfg->task, &scfg->tbuf); uring_task_init(&scfg->exec_task, "exec", &scfg->task, server_exec_free); list_init(&scfg->remotes); list_init(&scfg->locals); list_init(&scfg->proxys); list_init(&scfg->rcons); scfg->idle_timeout = 0; list_add(&scfg->list, &cfg->servers); return scfg; }