#include #include #include #include #include #include #include #include #include #include #include "main.h" #include "uring.h" #include "config-parser.h" #include "server.h" #include "server-config.h" #include "server-config-options.h" static void scfg_dns_cb(struct dns_async *dns, bool (*server_cb)(struct server *, struct saddr *)) { struct server *server; struct sockaddr_in *in4; struct sockaddr_in6 *in6; struct saddr *saddr; struct addrinfo *results = NULL, *ai; int r; assert_return(dns && dns->priv && server_cb); server = dns->priv; debug(DBG_DNS, "called, dns: %p, name: %s, server: %p, server->name: %s", dns, dns->name, server, server->name); r = gai_error(&dns->gcb); if (r == EAI_INPROGRESS) { /* This shouldn't happen, assume we'll get called again */ error("called with request in progress"); return; } else if (r == EAI_CANCELED) { /* The server must be in the process of going away */ goto out; } else if (r < 0) { error("DNS lookup of %s:%s failed: %s", dns->name, dns->port, gai_strerror(r)); goto out; } results = dns->gcb.ar_result; for (ai = results; ai; ai = ai->ai_next) { saddr = zmalloc(sizeof(*saddr)); if (!saddr) { error("DNS lookup of %s:%s failed: %m", dns->name, dns->port); goto out; } switch (ai->ai_family) { case AF_INET: in4 = (struct sockaddr_in *)ai->ai_addr; saddr_set_ipv4(saddr, in4->sin_addr.s_addr, in4->sin_port); server_cb(server, saddr); break; case AF_INET6: in6 = (struct sockaddr_in6 *)ai->ai_addr; saddr_set_ipv6(saddr, &in6->sin6_addr, in6->sin6_port); server_cb(server, saddr); break; default: error("getaddrinfo(%s:%s): unknown address family (%i)", dns->name, dns->port, ai->ai_family); xfree(saddr); break; } } out: freeaddrinfo(results); list_del(&dns->list); xfree(dns); uring_task_put(&server->task); server_commit(server); } static void scfg_local_dns_cb(struct dns_async *dns) { assert_return(dns); scfg_dns_cb(dns, server_add_local); } static void scfg_remote_dns_cb(struct dns_async *dns) { assert_return(dns); scfg_dns_cb(dns, server_add_remote); } static void scfg_rcon_dns_cb(struct dns_async *dns) { assert_return(dns); scfg_dns_cb(dns, server_add_rcon); } static bool handle_dns(struct server *server, const char *type, struct cfg_value *value, dns_cb_t *async_cb, bool (*sync_cb)(struct server *, struct saddr *)) { struct saddr *saddr, *tmp; struct dns_async *dns; assert_return(server && type && value && async_cb && sync_cb, false); switch (value->type) { case CFG_VAL_TYPE_ADDRS: debug(DBG_DNS, "%s: got immediate addrs", type); list_for_each_entry_safe(saddr, tmp, &value->saddrs, list) { list_del(&saddr->list); sync_cb(server, saddr); } return true; case CFG_VAL_TYPE_ASYNC_ADDRS: debug(DBG_DNS, "%s: doing async lookup of DNS record: %p", type, value->dns_async); dns = value->dns_async; dns->cb = async_cb; dns->priv = server; list_add(&dns->list, &server->dnslookups); uring_task_get(&server->task); return true; default: return false; } } static void scfg_parse(struct server *server) { char *pos; assert_return(server); pos = server->tbuf.buf; if (!config_parse_header(SERVER_CFG_HEADER, &pos)) { verbose("%s: missing/invalid header", server->name); return; } while (true) { int key; const char *keyname; struct cfg_value value; if (!config_parse_line(server->name, &pos, scfg_key_map, &key, &keyname, &value, true)) break; if (key == SCFG_KEY_INVALID) break; debug(DBG_CFG, "%s: key %s", server->name, keyname); switch (key) { case SCFG_KEY_TYPE: if (streq(value.str, "proxy")) { if (!server_set_type(server, SERVER_TYPE_PROXY)) return; } else if (streq(value.str, "announce")) { if (!server_set_type(server, SERVER_TYPE_ANNOUNCE)) return; } break; case SCFG_KEY_NAME: if (!server_set_pretty_name(server, value.str)) return; break; case SCFG_KEY_PORT: if (!server_set_port(server, value.uint16)) return; break; case SCFG_KEY_LOCAL: if (!handle_dns(server, "local", &value, scfg_local_dns_cb, server_add_local)) return; break; case SCFG_KEY_REMOTE: if (!handle_dns(server, "remote", &value, scfg_remote_dns_cb, server_add_remote)) return; break; case SCFG_KEY_IDLE_TIMEOUT: if (!server_set_idle_timeout(server, value.uint16)) return; break; case SCFG_KEY_STOP_METHOD: if (streq(value.str, "exec")) { if (server_set_stop_method(server, SERVER_STOP_METHOD_EXEC)) break; } else if (streq(value.str, "rcon")) { if (server_set_stop_method(server, SERVER_STOP_METHOD_RCON)) break; } else if (streq(value.str, "systemd")) { if (server_set_stop_method(server, SERVER_STOP_METHOD_SYSTEMD)) break; } return; case SCFG_KEY_START_METHOD: if (streq(value.str, "exec")) { if (server_set_start_method(server, SERVER_START_METHOD_EXEC)) break; } else if (streq(value.str, "systemd")) { if (server_set_start_method(server, SERVER_START_METHOD_SYSTEMD)) break; } return; case SCFG_KEY_STOP_EXEC: if (!server_set_stop_exec(server, value.str)) return; break; case SCFG_KEY_START_EXEC: if (!server_set_start_exec(server, value.str)) return; break; case SCFG_KEY_RCON: if (!handle_dns(server, "rcon", &value, scfg_rcon_dns_cb, server_add_rcon)) return; break; case SCFG_KEY_RCON_PASSWORD: if (!server_set_rcon_password(server, value.str)) return; break; case SCFG_KEY_SYSTEMD_SERVICE: if (!server_set_systemd_service(server, value.str)) return; break; case SCFG_KEY_INVALID: default: break; } } } static void scfg_read_cb(struct uring_task *task, int res) { struct server *server = container_of(task, struct server, task); assert_return(task); assert_task_alive(DBG_CFG, task); if (res <= 0) { error("error reading config file for %s: %s", server->name, strerror(-res)); server_delete(server); } debug(DBG_CFG, "%s: parsing cfg (%i bytes)", server->name, res); uring_task_close_fd(&server->task); scfg_parse(server); server_commit(server); } static void scfg_open_cb(struct uring_task *task, int res) { struct server *server = container_of(task, struct server, task); assert_return(task); assert_task_alive(DBG_CFG, task); if (res < 0) { error("open(%s) failed: %s", server->name, strerror(-res)); server_delete(server); return; } debug(DBG_CFG, "reading server cfg %s (fd %i)", server->name, res); uring_task_set_fd(&server->task, res); uring_tbuf_read_until_eof(&server->task, scfg_read_cb); } struct server_cfg_monitor { struct uring_task task; char buf[4096] _alignas_(struct inotify_event); }; static void scfgm_free(struct uring_task *task) { struct server_cfg_monitor *scfgm = container_of(task, struct server_cfg_monitor, task); assert_return(task); debug(DBG_CFG, "called"); xfree(scfgm); cfg->server_cfg_monitor = NULL; } static void inotify_event_dump(const struct inotify_event *event) { assert_return(event); debug(DBG_CFG, "inotify event:"); debug(DBG_CFG, " * WD : %i", event->wd); debug(DBG_CFG, " * Cookie : %" PRIu32, event->cookie); debug(DBG_CFG, " * Length : %" PRIu32, event->len); debug(DBG_CFG, " * Name : %s", event->name); debug(DBG_CFG, " * Mask : %" PRIu32, event->mask); if (event->mask & IN_ACCESS) debug(DBG_CFG, "\tIN_ACCESS"); else if(event->mask & IN_MODIFY) debug(DBG_CFG, "\tIN_MODIFY"); else if(event->mask & IN_ATTRIB) debug(DBG_CFG, "\tIN_ATTRIB"); else if(event->mask & IN_CLOSE_WRITE) debug(DBG_CFG, "\tIN_CLOSE_WRITE"); else if(event->mask & IN_CLOSE_NOWRITE) debug(DBG_CFG, "\tIN_CLOSE_NOWRITE"); else if(event->mask & IN_OPEN) debug(DBG_CFG, "\tIN_OPEN"); else if(event->mask & IN_MOVED_FROM) debug(DBG_CFG, "\tIN_MOVED_FROM"); else if(event->mask & IN_MOVED_TO) debug(DBG_CFG, "\tIN_MOVED_TO"); else if(event->mask & IN_CREATE) debug(DBG_CFG, "\tIN_CREATE"); else if(event->mask & IN_DELETE) debug(DBG_CFG, "\tIN_DELETE"); else if(event->mask & IN_DELETE_SELF) debug(DBG_CFG, "\tIN_DELETE_SELF"); else if(event->mask & IN_MOVE_SELF) debug(DBG_CFG, "\tIN_MOVE_SELF"); else if(event->mask & IN_UNMOUNT) debug(DBG_CFG, "\tIN_UNMOUNT"); else if(event->mask & IN_Q_OVERFLOW) debug(DBG_CFG, "\tIN_Q_OVERFLOW"); else if(event->mask & IN_IGNORED) debug(DBG_CFG, "\tIN_IGNORED"); } static void inotify_cb(struct uring_task *task, int res) { struct server_cfg_monitor *scfgm = container_of(task, struct server_cfg_monitor, task); const struct inotify_event *event; char *ptr; struct server *server; assert_return(task); assert_task_alive(DBG_CFG, task); if (res <= 0) { error("inotify_read: %i", res); return; } for (ptr = scfgm->buf; ptr < scfgm->buf + res; ptr += sizeof(struct inotify_event) + event->len) { event = (const struct inotify_event *)ptr; if (debug_enabled(DBG_CFG)) inotify_event_dump(event); if (event->mask & (IN_IGNORED | IN_MOVE_SELF | IN_DELETE_SELF | IN_UNMOUNT)) die("configuration directory gone, exiting"); if (event->mask & IN_Q_OVERFLOW) { error("inotify queue overflow"); continue; } if (!is_valid_server_config_filename(NULL, event->name)) continue; if (event->mask & (IN_MOVED_FROM | IN_DELETE)) server_delete_by_name(event->name); else if (event->mask & (IN_MOVED_TO | IN_CREATE | IN_CLOSE_WRITE)) { server = server_new(event->name); verbose("New server config file detected: %s", server->name); uring_openat(&server->task, server->name, scfg_open_cb); } else error("inotify: unknown event: 0x%08x", event->mask); } uring_read(&scfgm->task, scfgm->buf, sizeof(scfgm->buf), inotify_cb); } void server_cfg_monitor_refdump() { assert_return_silent(cfg->server_cfg_monitor); uring_task_refdump(&cfg->server_cfg_monitor->task); } void server_cfg_monitor_delete() { assert_return(cfg->server_cfg_monitor); debug(DBG_CFG, "closing fd %i", cfg->server_cfg_monitor->task.fd); uring_task_destroy(&cfg->server_cfg_monitor->task); cfg->server_cfg_monitor = NULL; } void server_cfg_monitor_init() { int ifd; int iwd; struct server_cfg_monitor *scfgm; DIR *dir; struct dirent *dent; struct server *server; assert_return(!cfg->server_cfg_monitor); scfgm = zmalloc(sizeof(*scfgm)); if (!scfgm) die("malloc: %m"); ifd = inotify_init1(IN_CLOEXEC); if (ifd < 0) die("inotify_init1: %m"); /* ln = IN_CREATE, cp/vi/mv = IN_CREATE, IN_OPEN, IN_CLOSE_WRITE */ iwd = inotify_add_watch(ifd, ".", IN_CLOSE_WRITE | IN_DELETE | IN_CREATE | IN_DELETE_SELF | IN_MOVE_SELF | IN_MOVED_TO | IN_MOVED_FROM | IN_DONT_FOLLOW | IN_EXCL_UNLINK | IN_ONLYDIR ); if (iwd < 0) die("inotify_add_watch: %m"); uring_task_init(&scfgm->task, "server-config-monitor", uring_parent(), scfgm_free); uring_task_set_fd(&scfgm->task, ifd); cfg->server_cfg_monitor = scfgm; uring_read(&scfgm->task, scfgm->buf, sizeof(scfgm->buf), inotify_cb); dir = opendir("."); if (!dir) die("opendir(%s): %m", cfg->cfg_dir); while ((dent = readdir(dir)) != NULL) { if (!is_valid_server_config_filename(dent, NULL)) continue; server = server_new(dent->d_name); if (server) uring_openat(&server->task, server->name, scfg_open_cb); } closedir(dir); }