From 8c27290245b7bcc7cd2f72f3b4a7562294b43bbe Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Tue, 23 Jun 2020 16:25:36 +0200 Subject: Split directories better --- announce.c | 116 ------ announce.h | 14 - config-parser.c | 490 ------------------------ config-parser.h | 59 --- idle.c | 378 ------------------- idle.h | 13 - igmp.c | 587 ----------------------------- igmp.h | 10 - main.c | 740 ------------------------------------ main.h | 175 --------- mcserverctl.c | 41 -- mcserverctl/mcserverctl.c | 40 ++ mcserverctl/meson.build | 14 + mcserverproxy/announce.c | 116 ++++++ mcserverproxy/announce.h | 14 + mcserverproxy/config-parser.c | 490 ++++++++++++++++++++++++ mcserverproxy/config-parser.h | 59 +++ mcserverproxy/idle.c | 378 +++++++++++++++++++ mcserverproxy/idle.h | 13 + mcserverproxy/igmp.c | 587 +++++++++++++++++++++++++++++ mcserverproxy/igmp.h | 10 + mcserverproxy/main.c | 740 ++++++++++++++++++++++++++++++++++++ mcserverproxy/main.h | 175 +++++++++ mcserverproxy/meson.build | 36 ++ mcserverproxy/ptimer.c | 223 +++++++++++ mcserverproxy/ptimer.h | 33 ++ mcserverproxy/server-config.c | 580 ++++++++++++++++++++++++++++ mcserverproxy/server-config.h | 10 + mcserverproxy/server-proxy.c | 578 ++++++++++++++++++++++++++++ mcserverproxy/server-proxy.h | 51 +++ mcserverproxy/server-rcon.c | 227 +++++++++++ mcserverproxy/server-rcon.h | 12 + mcserverproxy/server.c | 837 +++++++++++++++++++++++++++++++++++++++++ mcserverproxy/server.h | 128 +++++++ mcserverproxy/signal-handler.c | 195 ++++++++++ mcserverproxy/signal-handler.h | 8 + mcserverproxy/systemd.c | 219 +++++++++++ mcserverproxy/systemd.h | 14 + mcserverproxy/uring.c | 759 +++++++++++++++++++++++++++++++++++++ mcserverproxy/uring.h | 73 ++++ mcserverproxy/utils.c | 430 +++++++++++++++++++++ mcserverproxy/utils.h | 245 ++++++++++++ meson.build | 54 +-- ptimer.c | 223 ----------- ptimer.h | 33 -- rcon-protocol.c | 185 --------- rcon-protocol.h | 41 -- server-config.c | 580 ---------------------------- server-config.h | 10 - server-proxy.c | 578 ---------------------------- server-proxy.h | 51 --- server-rcon.c | 227 ----------- server-rcon.h | 12 - server.c | 837 ----------------------------------------- server.h | 128 ------- shared/meson.build | 22 ++ shared/rcon-protocol.c | 185 +++++++++ shared/rcon-protocol.h | 41 ++ signal-handler.c | 195 ---------- signal-handler.h | 8 - systemd.c | 219 ----------- systemd.h | 14 - uring.c | 759 ------------------------------------- uring.h | 73 ---- utils.c | 430 --------------------- utils.h | 245 ------------ 66 files changed, 7549 insertions(+), 7518 deletions(-) delete mode 100644 announce.c delete mode 100644 announce.h delete mode 100644 config-parser.c delete mode 100644 config-parser.h delete mode 100644 idle.c delete mode 100644 idle.h delete mode 100644 igmp.c delete mode 100644 igmp.h delete mode 100644 main.c delete mode 100644 main.h delete mode 100644 mcserverctl.c create mode 100644 mcserverctl/mcserverctl.c create mode 100644 mcserverctl/meson.build create mode 100644 mcserverproxy/announce.c create mode 100644 mcserverproxy/announce.h create mode 100644 mcserverproxy/config-parser.c create mode 100644 mcserverproxy/config-parser.h create mode 100644 mcserverproxy/idle.c create mode 100644 mcserverproxy/idle.h create mode 100644 mcserverproxy/igmp.c create mode 100644 mcserverproxy/igmp.h create mode 100644 mcserverproxy/main.c create mode 100644 mcserverproxy/main.h create mode 100644 mcserverproxy/meson.build create mode 100644 mcserverproxy/ptimer.c create mode 100644 mcserverproxy/ptimer.h create mode 100644 mcserverproxy/server-config.c create mode 100644 mcserverproxy/server-config.h create mode 100644 mcserverproxy/server-proxy.c create mode 100644 mcserverproxy/server-proxy.h create mode 100644 mcserverproxy/server-rcon.c create mode 100644 mcserverproxy/server-rcon.h create mode 100644 mcserverproxy/server.c create mode 100644 mcserverproxy/server.h create mode 100644 mcserverproxy/signal-handler.c create mode 100644 mcserverproxy/signal-handler.h create mode 100644 mcserverproxy/systemd.c create mode 100644 mcserverproxy/systemd.h create mode 100644 mcserverproxy/uring.c create mode 100644 mcserverproxy/uring.h create mode 100644 mcserverproxy/utils.c create mode 100644 mcserverproxy/utils.h delete mode 100644 ptimer.c delete mode 100644 ptimer.h delete mode 100644 rcon-protocol.c delete mode 100644 rcon-protocol.h delete mode 100644 server-config.c delete mode 100644 server-config.h delete mode 100644 server-proxy.c delete mode 100644 server-proxy.h delete mode 100644 server-rcon.c delete mode 100644 server-rcon.h delete mode 100644 server.c delete mode 100644 server.h create mode 100644 shared/meson.build create mode 100644 shared/rcon-protocol.c create mode 100644 shared/rcon-protocol.h delete mode 100644 signal-handler.c delete mode 100644 signal-handler.h delete mode 100644 systemd.c delete mode 100644 systemd.h delete mode 100644 uring.c delete mode 100644 uring.h delete mode 100644 utils.c delete mode 100644 utils.h diff --git a/announce.c b/announce.c deleted file mode 100644 index 13ef423..0000000 --- a/announce.c +++ /dev/null @@ -1,116 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "main.h" -#include "uring.h" -#include "announce.h" -#include "server.h" -#include "ptimer.h" - -struct announce { - uint64_t value; - struct uring_task task; - struct ptimer_task ptask; - int mcast_fd; -}; - -static void -announce_cb(struct ptimer_task *ptask) -{ - struct announce *announce = container_of(ptask, struct announce, ptask); - struct server *server; - - assert_return(ptask); - assert_task_alive(DBG_ANN, &announce->task); - - debug(DBG_ANN, "announcing servers"); - list_for_each_entry(server, &cfg->servers, list) - server_announce(server, announce->mcast_fd); -} - -static void -announce_free(struct uring_task *task) -{ - struct announce *announce = container_of(task, struct announce, task); - - assert_return(task); - debug(DBG_ANN, "task %p, announce 0x%p, mcast_fd: %i", - task, announce, announce->mcast_fd); - close(announce->mcast_fd); - xfree(announce); -} - -void -announce_refdump() -{ - assert_return_silent(cfg->announce); - - uring_task_refdump(&cfg->announce->task); -} - -void -announce_delete() -{ - assert_return_silent(cfg->announce); - - debug(DBG_ANN, "called"); - announce_stop(); - uring_task_destroy(&cfg->announce->task); - cfg->announce = NULL; -} - -void -announce_stop() -{ - struct announce *announce = cfg->announce; - - assert_return_silent(announce); - - ptimer_del_task(&announce->ptask); -} - -void -announce_start(unsigned duration) -{ - struct announce *announce = cfg->announce; - unsigned times; - - assert_return_silent(announce); - - if (duration == 0) - times = 0; - else - times = MAX(announce->ptask.times, - DIV_ROUND_UP(duration, cfg->announce_interval)); - - announce->ptask.times = times; - ptimer_add_task(&announce->ptask); -} - -void -announce_init() -{ - struct announce *announce; - int sfd; - - assert_return(!cfg->announce); - assert_return_silent(cfg->announce_interval > 0); - - announce = zmalloc(sizeof(*announce)); - if (!announce) - die("malloc: %m"); - - sfd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (sfd < 0) - die("socket: %m"); - - uring_task_init(&announce->task, "announce", uring_parent(), announce_free); - ptask_init(&announce->ptask, cfg->announce_interval, 0, announce_cb); - announce->mcast_fd = sfd; - cfg->announce = announce; -} - diff --git a/announce.h b/announce.h deleted file mode 100644 index 77a36f2..0000000 --- a/announce.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef fooannouncehfoo -#define fooannouncehfoo - -void announce_refdump(); - -void announce_delete(); - -void announce_stop(); - -void announce_start(unsigned duration); - -void announce_init(); - -#endif diff --git a/config-parser.c b/config-parser.c deleted file mode 100644 index ffed7f1..0000000 --- a/config-parser.c +++ /dev/null @@ -1,490 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include - -#include "main.h" -#include "config-parser.h" - -static void -eat_whitespace_and_comments(char **pos) -{ - assert_return(pos && *pos); - - while (true) { - while (isspace(**pos)) - (*pos)++; - - if (**pos != '#') - return; - - while (**pos != '\r' && **pos != '\n' && **pos != '\0') - (*pos)++; - } -} - -static char * -get_line(char **pos) -{ - char *begin, *end; - - assert_return(pos && *pos, NULL); - - begin = *pos; - while (isspace(*begin)) - begin++; - - if (*begin == '\0') - return NULL; - - end = begin; - while (*end != '\n' && *end != '\0') - end++; - - if (*end == '\0') - *pos = end; - else - *pos = end + 1; - - while (isspace(*end)) { - *end = '\0'; - end--; - } - - return begin; -} - -static bool -dnslookup(const char *name, uint16_t port, struct cfg_value *rvalue, bool async) -{ - struct sockaddr_in *in4; - struct sockaddr_in6 *in6; - struct dns_async tmp; - struct dns_async *dns; - int mode = async ? GAI_NOWAIT : GAI_WAIT; - struct addrinfo *results = NULL, *ai; - struct saddr *saddr = NULL; - bool rv = false; - int r; - - assert_return(!empty_str(name) && strlen(name) < sizeof(dns->name) && port > 0 && rvalue, false); - - if (async) { - rvalue->type = CFG_VAL_TYPE_ASYNC_ADDRS; - rvalue->dns_async = NULL; - dns = zmalloc(sizeof(*dns)); - if (!dns) { - error("async DNS lookup of %s failed: %m", name); - goto out; - } - debug(DBG_DNS, "doing async DNS lookup of %s: %p", name, dns); - } else { - memset(&tmp, 0, sizeof(tmp)); - dns = &tmp; - debug(DBG_DNS, "doing sync DNS lookup of %s", name); - } - - sprintf(dns->name, "%s", name); - sprintf(dns->port, "%" PRIu16, port); - - dns->req.ai_family = AF_UNSPEC; - dns->req.ai_socktype = SOCK_STREAM; - dns->req.ai_protocol = 0; - dns->req.ai_flags = AI_NUMERICSERV; - - dns->sev.sigev_notify = SIGEV_SIGNAL; - dns->sev.sigev_signo = SIGUSR1; - dns->sev.sigev_value.sival_ptr = dns; - - dns->gcb.ar_name = dns->name; - dns->gcb.ar_service = dns->port; - dns->gcb.ar_request = &dns->req; - - struct gaicb *gcbs[] = { &dns->gcb }; - - r = getaddrinfo_a(mode, gcbs, ARRAY_SIZE(gcbs), &dns->sev); - if (r != 0) { - error("getaddrinfo(%s:%" PRIu16 "): %s", name, port, gai_strerror(r)); - goto out; - } - - if (async) { - rvalue->dns_async = dns; - rv = true; - goto out; - } - - results = dns->gcb.ar_result; - - for (ai = results; ai; ai = ai->ai_next) { - saddr = zmalloc(sizeof(*saddr)); - if (!saddr) { - error("sync DNS lookup of %s failed: %m", name); - 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); - error("addrstr: %s", saddr->addrstr); - list_add(&saddr->list, &rvalue->saddrs); - break; - - case AF_INET6: - in6 = (struct sockaddr_in6 *)ai->ai_addr; - saddr_set_ipv6(saddr, &in6->sin6_addr, in6->sin6_port); - error("addrstr: %s", saddr->addrstr); - list_add(&saddr->list, &rvalue->saddrs); - break; - - default: - error("getaddrinfo(%s:%s): unknown address family (%i)", - dns->name, dns->port, ai->ai_family); - xfree(saddr); - break; - } - } - - rv = true; - -out: - freeaddrinfo(results); - return rv; -} - -static bool -strtosockaddrs(const char *str, struct cfg_value *rvalue, bool async) -{ - struct saddr *saddr; - uint16_t port; - char *tmp; - struct list_head *list; - unsigned naddrs = 0; - - assert_return(!empty_str(str) && rvalue, false); - - rvalue->type = CFG_VAL_TYPE_ADDRS; - list = &rvalue->saddrs; - list_init(list); - - if (*str == '[') { - /* IPv6, [a:b:c...h]:p or [*]:p */ - debug(DBG_CFG, "attempting to parse IPv6 addr (%s)", str); - - str++; - tmp = strchr(str, ']'); - if (!tmp) - goto error; - *tmp = '\0'; - - saddr = zmalloc(sizeof(*saddr)); - if (!saddr) - goto error; - - /* early list_add to make sure saddr is free():d on error */ - list_add(&saddr->list, list); - - if (streq(str, "*")) - saddr->in6.sin6_addr = in6addr_any; - else if (inet_pton(AF_INET6, str, &saddr->in6.sin6_addr) <= 0) - goto error; - - tmp++; - if (*tmp != ':') - goto error; - - tmp++; - if (strtou16_strict(tmp, &port) < 0) - goto error; - - saddr_set_ipv6(saddr, NULL, htons(port)); - naddrs++; - - } else if (*str == '*') { - /* IPv4, *:p */ - debug(DBG_CFG, "attempting to parse IPv4 addr (%s)", str); - - str++; - if (*str != ':') - goto error; - - str++; - if (strtou16_strict(str, &port) < 0) - goto error; - - saddr = zmalloc(sizeof(*saddr)); - if (!saddr) - goto error; - - saddr_set_ipv4(saddr, INADDR_ANY, htons(port)); - list_add(&saddr->list, list); - naddrs++; - - } else if ((tmp = strchr(str, ':'))) { - /* IPv4, a.b.c.d:p or IPv4/6 hostname:p */ - debug(DBG_CFG, "attempting to parse IPv4 addr or hostname (%s)", str); - - *tmp = '\0'; - tmp++; - if (strtou16_strict(tmp, &port) < 0) - goto error; - - saddr = zmalloc(sizeof(*saddr)); - if (!saddr) - goto error; - - if (inet_pton(AF_INET, str, &saddr->in4.sin_addr) > 0) { - debug(DBG_CFG, "got an IPv4:port (%s:%" PRIu16 ")", str, port); - saddr_set_ipv4(saddr, saddr->in4.sin_addr.s_addr, htons(port)); - list_add(&saddr->list, list); - naddrs++; - goto success; - } - - xfree(saddr); - debug(DBG_CFG, "maybe got a hostname:port (%s:%" PRIu16 ")", str, port); - if (!dnslookup(str, port, rvalue, async)) - goto error; - - } else if (strtou16_strict(tmp, &port) == 0) { - /* Port */ - debug(DBG_CFG, "attempting to parse a port number (%s)", str); - - saddr = zmalloc(sizeof(*saddr)); - if (!saddr) - goto error; - - saddr_set_ipv6(saddr, &in6addr_any, htons(port)); - list_add(&saddr->list, list); - naddrs++; - - saddr = zmalloc(sizeof(*saddr)); - if (!saddr) - goto error; - - saddr_set_ipv4(saddr, INADDR_ANY, htons(port)); - list_add(&saddr->list, list); - naddrs++; - - } else { - /* Unknown */ - error("unable to parse address: %s", str); - goto error; - } - -success: - switch (rvalue->type) { - case CFG_VAL_TYPE_ADDRS: - if (list_empty(list) || naddrs == 0) { - error("empty address list"); - return false; - } - - debug(DBG_CFG, "parsed to %u addresses", naddrs); - return true; - - case CFG_VAL_TYPE_ASYNC_ADDRS: - debug(DBG_CFG, "looking up address asynchronously"); - return true; - - default: - error("invalid rvalue type"); - rvalue->type = CFG_VAL_TYPE_INVALID; - break; - } - -error: - if (rvalue->type == CFG_VAL_TYPE_ADDRS && !list_empty(list)) { - struct saddr *tmp; - - list_for_each_entry_safe(saddr, tmp, list, list) { - list_del(&saddr->list); - xfree(saddr); - } - } - return false; -} - -/* Returns true if there's data left to parse in buf */ -bool -config_parse_line(const char *filename, char **buf, - struct cfg_key_value_map *kvmap, int *rkey, - const char **rkeyname, struct cfg_value *rvalue) -{ - char *line, *tmp, *key; - int i; - - assert_return(buf && *buf && kvmap && rkey && rkeyname && rvalue, false); - - eat_whitespace_and_comments(buf); - line = get_line(buf); - if (!line) - return false; - - debug(DBG_CFG, "%s: parsing config line: %s", filename, line); - - tmp = line; - while (isspace(*tmp)) - tmp++; - - if (*tmp == '\0') - goto error; - - key = tmp; - while (*tmp != '\0' && !isspace(*tmp)) - tmp++; - - if (*tmp == '\0') - goto error; - - *tmp = '\0'; - tmp++; - - while (isspace(*tmp)) - tmp++; - - if (*tmp != '=') - goto error; - - tmp++; - while (isspace(*tmp)) - tmp++; - - if (*tmp == '\0') - goto error; - - for (i = 0; kvmap[i].key_name; i++) { - if (!streq(kvmap[i].key_name, key)) - continue; - - switch (kvmap[i].value_type) { - - case CFG_VAL_TYPE_STRING: - rvalue->type = CFG_VAL_TYPE_STRING; - rvalue->str = tmp; - break; - - case CFG_VAL_TYPE_UINT16: { - uint16_t v; - - if (strtou16_strict(tmp, &v) < 0) - goto error; - - rvalue->type = CFG_VAL_TYPE_UINT16; - rvalue->uint16 = v; - break; - } - - case CFG_VAL_TYPE_ADDRS: - if (!strtosockaddrs(tmp, rvalue, false)) - goto error; - - if (rvalue->type != CFG_VAL_TYPE_ADDRS) { - error("invalid type returned from strtosockaddrs"); - goto error; - } - - if (list_empty(&rvalue->saddrs)) { - error("empty address list"); - goto error; - } - break; - - case CFG_VAL_TYPE_ASYNC_ADDRS: - if (!strtosockaddrs(tmp, rvalue, true)) - goto error; - - switch (rvalue->type) { - case CFG_VAL_TYPE_ADDRS: - if (list_empty(&rvalue->saddrs)) { - error("empty address list"); - goto error; - } - break; - - case CFG_VAL_TYPE_ASYNC_ADDRS: - if (!rvalue->dns_async) { - error("dns_async not set"); - goto error; - } - break; - - default: - error("invalid type returned from strtosockaddrs"); - goto error; - } - - break; - - case CFG_VAL_TYPE_BOOL: - if (strcaseeq(tmp, "yes") || strcaseeq(tmp, "true")) { - rvalue->type = CFG_VAL_TYPE_BOOL; - rvalue->boolean = true; - } else if (strcaseeq(tmp, "no") || strcaseeq(tmp, "false")) { - rvalue->type = CFG_VAL_TYPE_BOOL; - rvalue->boolean = false; - } else { - error("invalid boolean value (%s)", tmp); - goto error; - } - break; - - case CFG_VAL_TYPE_INVALID: - /* fall through */ - default: - goto error; - } - - /* sanity check */ - if ((rvalue->type != kvmap[i].value_type) && - ((kvmap[i].value_type != CFG_VAL_TYPE_ASYNC_ADDRS) && - (rvalue->type != CFG_VAL_TYPE_ADDRS))) { - error("rvalue->type != kvmap->type"); - goto error; - } - - *rkey = kvmap[i].key_value; - *rkeyname = kvmap[i].key_name; - return true; - } - -error: - /* FIXME: the line is already mangled here, a line number would be nice */ - error("%s: invalid config line: %s", filename, line); - rvalue->type = CFG_VAL_TYPE_INVALID; - *rkey = 0; - *rkeyname = NULL; - return true; -} - -bool -config_parse_header(const char *filename, const char *title, char **buf) -{ - char *line; - - assert_return(!empty_str(filename) && !empty_str(title) && buf && *buf, false); - - eat_whitespace_and_comments(buf); - - line = get_line(buf); - if (!line) { - error("%s: missing header in configuration file", filename); - return false; - } else { - char titlehdr[strlen(title) + 3]; - - sprintf(titlehdr, "[%s]", title); - if (!streq(line, titlehdr)) { - error("%s: incorrect header in configuration file", filename); - return false; - } - } - - return true; -} diff --git a/config-parser.h b/config-parser.h deleted file mode 100644 index 3a117a3..0000000 --- a/config-parser.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef fooconfigparserhfoo -#define fooconfigparserhfoo - -#define _GNU_SOURCE -#include -#include -#include -#include - -enum cfg_value_type { - CFG_VAL_TYPE_INVALID, - CFG_VAL_TYPE_STRING, - CFG_VAL_TYPE_UINT16, - CFG_VAL_TYPE_ADDRS, - CFG_VAL_TYPE_ASYNC_ADDRS, - CFG_VAL_TYPE_BOOL, -}; - -struct dns_async; - -typedef void (dns_cb_t)(struct dns_async *); - -struct dns_async { - char name[FQDN_STR_LEN + 1]; - char port[PORT_STR_LEN + 1]; - struct addrinfo req; - struct gaicb gcb; - struct sigevent sev; - dns_cb_t *cb; - void *priv; - struct list_head list; -}; - -struct cfg_key_value_map { - const char *key_name; - int key_value; - enum cfg_value_type value_type; -}; - -struct cfg_value { - enum cfg_value_type type; - union { - const char *str; - uint16_t uint16; - struct list_head saddrs; - struct dns_async *dns_async; - bool boolean; - }; -}; - -bool config_parse_line(const char *filename, char **buf, - struct cfg_key_value_map *kvmap, - int *rkey, const char **rkeyname, - struct cfg_value *rvalue); - -bool config_parse_header(const char *filename, - const char *title, char **buf); - -#endif diff --git a/idle.c b/idle.c deleted file mode 100644 index c49846d..0000000 --- a/idle.c +++ /dev/null @@ -1,378 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include - -#include "main.h" -#include "uring.h" -#include "server.h" -#include "idle.h" -#include "ptimer.h" - -struct idle { - struct ptimer_task ptask; - struct uring_task task; -}; - -static inline void -write_byte(char **pos, char byte) -{ - assert_return(pos && *pos); - - **pos = byte; - (*pos)++; -} - -#define MC_HELO 0x00 -#define MC_NEXT_STATE_STATUS 0x01 -#define MC_GET_STATUS 0x00 -#define MC_VARINT_MAX_BYTES 5 -#define MC_STATUS_REPLY 0x00 -#define MC_UNDEFINED_VERSION -1 - -static inline void -write_varint(char **pos, int32_t orig) -{ - assert_return(pos && *pos); - - uint32_t val = (uint32_t)orig; - - while (val) { - **pos = val & 0x7f; - val >>= 7; - if (val > 0) - **pos |= 0x80; - (*pos)++; - } -} - -/* - * return value: - * positive = varint parsed - * zero = need more bytes - * negative = error - */ -static inline int -read_varint(char **pos, size_t *remain, int32_t *res) -{ - unsigned consumed; - uint32_t val = 0; - - assert_return(pos && *pos && remain && res, -1); - - for (consumed = 1; consumed <= *remain; consumed++) { - uint32_t tmp; - - tmp = **pos & 0x7f; - val += (tmp << (7 * (consumed - 1))); - (*pos)++; - - if (!(tmp & 0x80)) - break; - } - - if (consumed > *remain) - return 0; - else if (consumed > MC_VARINT_MAX_BYTES) - return -1; - - *remain -= consumed; - *res = (int32_t)val; - return 1; -} - -static inline void -write_bytes(char **pos, const char *bytes, size_t n) -{ - assert_return(pos && *pos && bytes && n > 0); - - memcpy(*pos, bytes, n); - *pos += n; -} - -static inline void -write_str(char **pos, const char *str) -{ - size_t len; - - assert_return(pos && *pos && !empty_str(str)); - - len = strlen(str); - write_varint(pos, len); - write_bytes(pos, str, len); -} - -static inline void -write_cmd(char **pos, const char *begin, const char *end) -{ - assert_return(pos && *pos && begin && end && end > begin); - - write_varint(pos, end - begin); - write_bytes(pos, begin, end - begin); -} - -static int -idle_check_handshake_complete(struct uring_task *task, int res) -{ - size_t remain; - char *pos; - int32_t mclen; - int r; - - assert_return(task, -EINVAL); - assert_task_alive_or(DBG_IDLE, task, return -EINTR); - - remain = task->tbuf->len; - pos = task->tbuf->buf; - - r = read_varint(&pos, &remain, &mclen); - if (r < 0) { - error("failed to parse message length"); - return -EINVAL; - } else if (r == 0) { - return 0; - } else if (mclen < 2) { - error("short MC message"); - return -EINVAL; - } - - if (mclen < remain) { - debug(DBG_IDLE, "short MC message - len: %" PRIi32 ", remain: %zu", - mclen, remain); - return 0; - } - - debug(DBG_IDLE, "Complete message"); - return 1; -} - -#define ONLINE_NEEDLE "\"online\"" -static int -get_player_count(const char *pos, size_t remain) -{ - /* - * Example JSON (line breaks added): - * {"description":{ - * "text":"A Minecraft Server"}, - * "players":{"max":20,"online":0}, - * "version":{"name":"1.15.2","protocol":578} - * } - */ - char *online; - char *end; - unsigned count; - - assert_return(pos && remain > 0, -1); - - online = memmem(pos, remain, ONLINE_NEEDLE, strlen(ONLINE_NEEDLE)); - if (!online) { - error("could not find online count in JSON"); - return -1; - } - - remain -= (online - pos); - - end = memchr(online, '}', remain); - if (!end) { - error("could not parse JSON (no end)"); - return -1; - } - *end = '\0'; - - if (sscanf(online, ONLINE_NEEDLE " : %u", &count) != 1) { - error("could not parse JSON (online count)"); - return -1; - } - - return count; -} - -static void -idle_check_handshake_reply(struct uring_task *task, int res) -{ - struct server *server = container_of(task, struct server, idle_task); - int32_t mclen; - int32_t jsonlen; - char *pos; - size_t remain; - int player_count = -1; - int r; - - assert_return(task); - assert_task_alive(DBG_IDLE, task); - - debug(DBG_IDLE, "res: %i", res); - if (res < 0) - goto out; - - /* - fprintf(stderr, "Received MC message (%i bytes):", res); - for (int i = 0; i < res; i++) - fprintf(stderr, "0x%02hhx ", idle->remotebuf[i]); - fprintf(stderr, "n"); - */ - - remain = server->idle_buf.len; - pos = server->idle_buf.buf; - - r = read_varint(&pos, &remain, &mclen); - if (r <= 0 || mclen < 2 || mclen < remain) { - /* Should not happen since the msg has been checked already */ - error("invalid message"); - goto out; - } - - debug(DBG_IDLE, "MC message - len: %" PRIi32 ", remain: %zu", - mclen, remain); - - if (*pos != MC_STATUS_REPLY) { - error("unknown server reply (0x%02hhx)", *pos); - goto out; - } - - pos++; - remain--; - - r = read_varint(&pos, &remain, &jsonlen); - if (r <= 0) { - error("could not read JSON length"); - goto out; - } - - debug(DBG_IDLE, "MC - json len: %" PRIi32 ", remain: %zu", - jsonlen, remain); - - if (jsonlen < remain) { - error("invalid JSON length"); - goto out; - } - - /* - fprintf(stderr, "JSON: "); - for (int i = 0; i < jsonlen; i++) - fprintf(stderr, "%c", pos[i]); - */ - - player_count = get_player_count(pos, remain); - -out: - uring_task_close_fd(task); - server_set_active_players(server, player_count); - return; -} - -static void -idle_check_handshake_sent(struct uring_task *task, int res) -{ - assert_return(task); - assert_task_alive(DBG_IDLE, task); - - debug(DBG_IDLE, "sent %i bytes", res); - if (res < 0) { - uring_task_close_fd(task); - return; - } - - uring_tbuf_read_until(task, - idle_check_handshake_complete, - idle_check_handshake_reply); -} - -void -idle_check_get_player_count(struct server *server, struct connection *conn) -{ - char buf[1024]; - char *pos; - char *cmdbuf = server->idle_buf.buf; - uint16_t port; - char hostname[INET6_ADDRSTRLEN]; - - assert_return(server && conn && server->idle_task.priv); - - port = saddr_port(&conn->remote); - saddr_addr(&conn->remote, hostname, sizeof(hostname)); - - pos = buf; - write_byte(&pos, MC_HELO); - write_varint(&pos, MC_UNDEFINED_VERSION); - write_str(&pos, hostname); - write_byte(&pos, (port >> 8) & 0xff); - write_byte(&pos, (port >> 0) & 0xff); - write_byte(&pos, MC_NEXT_STATE_STATUS); - write_cmd(&cmdbuf, buf, pos); - - pos = buf; - write_byte(&pos, MC_GET_STATUS); - write_cmd(&cmdbuf, buf, pos); - - server->idle_buf.len = (cmdbuf - server->idle_buf.buf); - debug(DBG_IDLE, "sending MC message (%zu bytes)", server->idle_buf.len); - - uring_tbuf_write(&server->idle_task, idle_check_handshake_sent); -} - -static void -idle_cb(struct ptimer_task *ptask) -{ - struct idle *idle = container_of(ptask, struct idle, ptask); - struct server *server; - - assert_return(ptask); - assert_task_alive(DBG_IDLE, &idle->task); - - debug(DBG_IDLE, "timer fired"); - - list_for_each_entry(server, &cfg->servers, list) - server_idle_check(server); -} - -static void -idle_free(struct uring_task *task) -{ - struct idle *idle = container_of(task, struct idle, task); - - assert_return(task); - debug(DBG_IDLE, "task %p, idle %p", task, idle); - xfree(idle); -} - -void -idle_refdump() -{ - assert_return_silent(cfg->idle); - - uring_task_refdump(&cfg->idle->task); -} - -void -idle_delete() -{ - assert_return(cfg->idle); - - debug(DBG_IDLE, "closing fd %i", cfg->idle->task.fd); - ptimer_del_task(&cfg->idle->ptask); - uring_task_destroy(&cfg->idle->task); - cfg->idle = NULL; -} - -void -idle_init() -{ - struct idle *idle; - - assert_return(!cfg->idle); - - idle = zmalloc(sizeof(*idle)); - if (!idle) - die("malloc: %m"); - - ptask_init(&idle->ptask, 60, 0, idle_cb); - uring_task_init(&idle->task, "idle", uring_parent(), idle_free); - ptimer_add_task(&idle->ptask); - cfg->idle = idle; -} - diff --git a/idle.h b/idle.h deleted file mode 100644 index d7e4ab0..0000000 --- a/idle.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef fooidlehfoo -#define fooidlehfoo - -void idle_check_get_player_count(struct server *server, - struct connection *conn); - -void idle_refdump(); - -void idle_delete(); - -void idle_init(); - -#endif diff --git a/igmp.c b/igmp.c deleted file mode 100644 index dc43a9f..0000000 --- a/igmp.c +++ /dev/null @@ -1,587 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "main.h" -#include "uring.h" -#include "igmp.h" -#include "announce.h" - -struct igmp { - struct uring_task task; - struct uring_task_buf tbuf; -}; - -#define ETH_HDR_LEN 14 -#define IPV4_MIN_HDR_LEN 20 -#define IGMP_MIN_LEN 8 - -struct __attribute__((packed, scalar_storage_order("big-endian"))) ipv4_hdr { - unsigned version:4; - unsigned ihl:4; - unsigned dscp:6; - unsigned ecn:2; - unsigned length:16; - unsigned id:16; - unsigned flags:3; - unsigned fragment_offset:13; - unsigned ttl:8; - unsigned protocol:8; - unsigned checksum:16; - unsigned src:32; - unsigned dst:32; - unsigned options[]; -}; - -enum igmp_type { - IGMP_MEMBERSHIP_QUERY = 0x11, - IGMP_V1_MEMBERSHIP_REPORT = 0x12, - IGMP_V2_MEMBERSHIP_REPORT = 0x16, - IGMP_V3_MEMBERSHIP_REPORT = 0x22, - IGMP_V2_LEAVE_GROUP = 0x17 -}; - -union igmp_msg { - struct __attribute__((packed, scalar_storage_order("big-endian"))) { - enum igmp_type type:8; - unsigned unknown:8; - unsigned checksum:16; - } common; - - struct __attribute__((packed, scalar_storage_order("big-endian"))) { - enum igmp_type type:8; - unsigned resptime:8; - unsigned checksum:16; - unsigned addr:32; - } v2; - - struct __attribute__((packed, scalar_storage_order("big-endian"))) { - enum igmp_type type:8; - unsigned reserved1:8; - unsigned checksum:16; - unsigned reserved2:16; - unsigned nrecs:16; - uint8_t records[]; - } v3; -}; - -enum igmp_v3_record_type { - IGMP_V3_REC_MODE_IS_INCL = 1, - IGMP_V3_REC_MODE_IS_EXCL = 2, - IGMP_V3_REC_MODE_CH_INCL = 3, - IGMP_V3_REC_MODE_CH_EXCL = 4 -}; - -union igmp_v3_record { - struct __attribute__((packed, scalar_storage_order("big-endian"))) { - enum igmp_v3_record_type type:8; - unsigned auxlen:8; - unsigned nsrcs:16; - unsigned addr:32; - uint32_t saddr[]; - }; -}; - -static inline unsigned short -from32to16(unsigned int x) -{ - /* add up 16-bit and 16-bit for 16+c bit */ - x = (x & 0xffff) + (x >> 16); - /* add up carry.. */ - x = (x & 0xffff) + (x >> 16); - return x; -} - -static unsigned int -do_csum(const unsigned char *buf, int len) -{ - int odd; - unsigned int result = 0; - - assert_return(buf && len > 0, 0); - - odd = 1 & (unsigned long)buf; - if (odd) { -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - result += (*buf << 8); -#else - result = *buf; -#endif - len--; - buf++; - } - - if (len >= 2) { - if (2 & (unsigned long)buf) { - result += *(unsigned short *)buf; - len -= 2; - buf += 2; - } - if (len >= 4) { - const unsigned char *end = buf + ((unsigned)len & ~3); - unsigned int carry = 0; - do { - unsigned int w = *(unsigned int *)buf; - buf += 4; - result += carry; - result += w; - carry = (w > result); - } while (buf < end); - result += carry; - result = (result & 0xffff) + (result >> 16); - } - if (len & 2) { - result += *(unsigned short *)buf; - buf += 2; - } - } - - if (len & 1) -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - result += *buf; -#else - result += (*buf << 8); -#endif - - result = from32to16(result); - if (odd) - result = ((result >> 8) & 0xff) | ((result & 0xff) << 8); - - return result; -} - -static inline bool -csum_valid(const char *buf, size_t len) -{ - assert_return(buf && len > 0, false); - - return do_csum((unsigned const char *)buf, len) == 0xffff; -} - -static void -igmp_match() -{ - debug(DBG_IGMP, "multicast request discovered"); - /* - * IGMP messages are sent with approx 120-130 sec intervals, - * so set time to 5 minutes to allow some slack. - */ - announce_start(5 * 60); -} - -static void -igmp_parse(struct igmp *igmp) -{ - char *buf; - size_t len; - struct ipv4_hdr *hdr; - size_t body_len; - union igmp_msg *igmp_msg; - - assert_return(igmp); - - buf = igmp->task.tbuf->buf; - len = igmp->task.tbuf->len; - hdr = (struct ipv4_hdr *)buf; - - if (len <= IPV4_MIN_HDR_LEN) - return; - - if (hdr->version != 4) - return; - - if (hdr->ihl * 4 < IPV4_MIN_HDR_LEN) - return; - - if (hdr->length < hdr->ihl * 4) - return; - - if (hdr->length != len) - return; - - if (hdr->fragment_offset > 0) - return; - - if (hdr->protocol != IPPROTO_IGMP) - return; - - if (!csum_valid(buf, hdr->ihl * 4)) - return; - - body_len = hdr->length - hdr->ihl * 4; - igmp_msg = (union igmp_msg *)(buf + hdr->ihl * 4); - - if (body_len < IGMP_MIN_LEN) - return; - - switch (igmp_msg->common.type) { - case IGMP_V1_MEMBERSHIP_REPORT: - debug(DBG_IGMP, "igmp_v1_membership_report"); - /* fall through */ - - case IGMP_V2_MEMBERSHIP_REPORT: { - struct in_addr src; - char src_str[INET_ADDRSTRLEN]; - struct in_addr dst; - char dst_str[INET_ADDRSTRLEN]; - struct in_addr grp; - char grp_str[INET_ADDRSTRLEN]; - - src.s_addr = htonl(hdr->src); - inet_ntop(AF_INET, &src, src_str, sizeof(src_str)); - dst.s_addr = htonl(hdr->dst); - inet_ntop(AF_INET, &dst, dst_str, sizeof(dst_str)); - grp.s_addr = htonl(igmp_msg->v2.addr); - inet_ntop(AF_INET, &grp, grp_str, sizeof(grp_str)); - - debug(DBG_IGMP, "igmp_v2_membership_report %s -> %s (%s)", - src_str, dst_str, grp_str); - - if (body_len != IGMP_MIN_LEN) { - error("IGMPv2 invalid size"); - break; - } - - if (!csum_valid((char *)igmp_msg, body_len)) { - error("IGMPv2 invalid checksum"); - break; - } - - debug(DBG_IGMP, "Inet addr: 0x%x", inet_addr("224.0.2.60")); - debug(DBG_IGMP, "Inet addr: 0x%x", cinet_addr(224,0,2,60)); - debug(DBG_IGMP, "Inet addr: 0x%x", chtobe32(cinet_addr(224,0,2,60))); - if (htonl(hdr->dst) != cinet_addr(224,0,2,60)) { - debug(DBG_IGMP, "IGMPv2 invalid dst addr"); - break; - } - - if (htonl(igmp_msg->v2.addr) != cinet_addr(224,0,2,60)) { - debug(DBG_IGMP, "IGMPv2 invalid grp addr"); - break; - } - - igmp_match(); - break; - } - - case IGMP_V3_MEMBERSHIP_REPORT: { - char *pos = (char *)igmp_msg; - struct in_addr src; - char src_str[INET_ADDRSTRLEN]; - struct in_addr dst; - char dst_str[INET_ADDRSTRLEN]; - - src.s_addr = htonl(hdr->src); - inet_ntop(AF_INET, &src, src_str, sizeof(src_str)); - dst.s_addr = htonl(hdr->dst); - inet_ntop(AF_INET, &dst, dst_str, sizeof(dst_str)); - - debug(DBG_IGMP, "igmp_v3_membership_report %s -> %s", - src_str, dst_str); - - debug(DBG_IGMP, "IGMPv3" - " type: %x," - " reserved: %u," - " csum: %u," - " reserved: %u," - " nrecs: %u," - " size: %zu\n", - igmp_msg->v3.type, - igmp_msg->v3.reserved1, - igmp_msg->v3.checksum, - igmp_msg->v3.reserved2, - igmp_msg->v3.nrecs, - sizeof(igmp_msg->v3)); - - if (!csum_valid(pos, body_len)) { - error("IGMPv3 csum invalid"); - break; - } - - if (htonl(hdr->dst) != cinet_addr(224,0,0,22)) { - debug(DBG_IGMP, "IGMPv2 invalid dst addr"); - break; - } - - body_len -= sizeof(igmp_msg->v3); - pos += sizeof(igmp_msg->v3); - - for (unsigned rec = 0; rec < igmp_msg->v3.nrecs; rec++) { - union igmp_v3_record *record = (union igmp_v3_record *)pos; - struct in_addr grp; - char grp_str[INET_ADDRSTRLEN]; - - if (body_len < sizeof(*record)) { - error("IGMPv3 too short"); - break; - } - - grp.s_addr = htonl(record->addr); - inet_ntop(AF_INET, &grp, grp_str, sizeof(grp_str)); - debug(DBG_IGMP, "received IGMPv3 record to %s", grp_str); - - debug(DBG_IGMP, "IGMPv3 rec, " - " type: %u," - " auxlen: %u," - " nsrcs: %u," - " addr: %s," - " size: %zu bytes", - record->type, - record->auxlen, - record->nsrcs, - grp_str, - sizeof(*record)); - - body_len -= sizeof(*record); - pos += sizeof(*record); - - if (body_len < record->nsrcs * sizeof(uint32_t) + record->auxlen) { - error("IGMPv3 too short"); - break; - } - - for (unsigned addr = 0; addr < record->nsrcs; addr++) { - struct in_addr grp_src; - char grp_src_str[INET_ADDRSTRLEN]; - - grp_src.s_addr = htonl(record->saddr[addr]); - inet_ntop(AF_INET, &grp_src, grp_src_str, sizeof(grp_src_str)); - debug(DBG_IGMP, "received IGMPv3 record src %s", - grp_src_str); - - body_len -= sizeof(record->saddr[addr]); - pos += sizeof(record->saddr[addr]); - } - - /* Yes, EXCL, not INCL, see RFC3376 */ - if ((htonl(record->addr) == cinet_addr(224,0,2,60)) && - ((record->type == IGMP_V3_REC_MODE_IS_EXCL) || - (record->type == IGMP_V3_REC_MODE_CH_EXCL))) - igmp_match(); - - body_len -= record->auxlen; - pos += record->auxlen; - } - - break; - } - - case IGMP_MEMBERSHIP_QUERY: - debug(DBG_IGMP, "igmp_membership_query"); - break; - - case IGMP_V2_LEAVE_GROUP: - debug(DBG_IGMP, "igmp_v2_leave_group"); - break; - - default: - debug(DBG_IGMP, "IGMP msg type %02hhx", igmp_msg->common.type); - break; - } - - buf += hdr->length; - len -= hdr->length; -} - -static void -igmp_read_cb(struct uring_task *task, int res) -{ - struct igmp *igmp = container_of(task, struct igmp, task); - - assert_return(task); - assert_task_alive(DBG_IGMP, task); - - debug(DBG_IGMP, "task %p, igmp %p, res %i", task, igmp, res); - if (res < 0) { - error("res: %i", res); - return; - } - - task->tbuf->len = res; - - if (task->saddr.storage.ss_family == AF_PACKET || - task->saddr.ll.sll_protocol == htons(ETH_P_IP)) - igmp_parse(igmp); - else - debug(DBG_IGMP, "invalid packet type received"); - - uring_tbuf_read(&igmp->task, igmp_read_cb); -} - -static void -igmp_free(struct uring_task *task) -{ - struct igmp *igmp = container_of(task, struct igmp, task); - - assert_return(task); - debug(DBG_IGMP, "task %p, igmp %p", task, igmp); - xfree(igmp); -} - -void -igmp_refdump() -{ - assert_return_silent(cfg->igmp); - - uring_task_refdump(&cfg->igmp->task); -} - -void -igmp_delete() -{ - assert_return_silent(cfg->igmp); - - debug(DBG_IGMP, "closing fd %i", cfg->igmp->task.fd); - uring_task_destroy(&cfg->igmp->task); - cfg->igmp = NULL; -} - -void -igmp_init() -{ - static const struct sock_filter filter[] = { - BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ - BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct iphdr), 1, 0), /* A < sizeof(iphdr) */ - BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ - - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 0 /* iphdr[0] */), /* A <- version + ihl */ - BPF_STMT(BPF_ALU + BPF_RSH + BPF_K, 4), /* A <- A >> 4 (version) */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x04, 1, 0), /* A != 4 */ - BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ - - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct iphdr, protocol)), /* A <- ip protocol */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_IGMP, 1, 0), /* A != IPPROTO_IGMP */ - BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ - - BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct iphdr, daddr)), /* A <- ip dst addr */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, chtobe32(cinet_addr(224,0,2,60)), 2, 0), /* A != 224.0.2.60 */ - //BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xe000023c, 2, 0), /* A != 224.0.2.60 */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, chtobe32(cinet_addr(224,0,0,22)), 1, 0), /* A != 224.0.0.22 */ - //BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xe0000016, 1, 0), /* A != 224.0.0.22 */ - BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ - - BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0 /* iphdr[0] */), /* X <- pkt->ihl * 4 */ - BPF_STMT(BPF_LD + BPF_IMM, 20), /* A <- 20 */ - BPF_JUMP(BPF_JMP + BPF_JGT + BPF_X, 0, 0, 1), /* A > X */ - BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ - - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct iphdr, tot_len)), /* A <- ip tot_len */ - BPF_JUMP(BPF_JMP + BPF_JGT + BPF_X, 0, 1, 0), /* A <= ip->ihl * 4 */ - BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ - BPF_STMT(BPF_ALU + BPF_SUB + BPF_X, 0), /* A <- A - X (bodylen) */ - BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, 8, 1, 0), /* A < 8 */ - BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ - - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct iphdr, tot_len)), /* A <- ip->tot_len */ - BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ - BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), /* A != ip->tot_len */ - BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ - - BPF_STMT(BPF_RET + BPF_K, (uint32_t) -1), /* accept packet */ - }; - static const struct sock_fprog fprog = { - .len = ARRAY_SIZE(filter), - .filter = (struct sock_filter*) filter, - }; - struct sockaddr_ll addr = { - .sll_family = AF_PACKET, - .sll_ifindex = 0, - .sll_pkttype = PACKET_MULTICAST, - }; - struct igmp *igmp; - int sfd; - int opt; - - if (!cfg->do_igmp) { - debug(DBG_IGMP, "igmp snooping disabled"); - return; - } - - assert_return(!cfg->igmp); - - igmp = zmalloc(sizeof(*igmp)); - if (!igmp) - return; - - /* - * Kernel limitation, must be ETH_P_ALL, not ETH_P_IP or we won't get - * outgoing packets, https://lkml.org/lkml/1999/12/23/112 - */ - sfd = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_ALL)); - if (sfd < 0) { - if (errno == EACCES || errno == EPERM) - info("Unable to do IGMP snooping, permission denied"); - else - error("socket: %m"); - goto error; - } - - if (setsockopt(sfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0) { - error("setsockopt(SO_ATTACH_FILTER): %m"); - goto error; - } - - if (setsockopt(sfd, SOL_SOCKET, SO_LOCK_FILTER, &opt, sizeof(opt)) < 0) { - error("setsockopt(SO_LOCK_FILTER): %m"); - goto error; - } - - if (cfg->igmp_iface) { - struct ifreq ifreq; - int r; - - r = snprintf(ifreq.ifr_name, sizeof(ifreq.ifr_name), - "%s", cfg->igmp_iface); - if (r < 0 || r >= sizeof(ifreq.ifr_name)) - die("invalid interface name: %s", cfg->igmp_iface); - - if (ioctl(sfd, SIOCGIFINDEX, &ifreq) < 0) - die("ioctl: %m"); - - debug(DBG_IGMP, "using interface %s (%i)", - cfg->igmp_iface, ifreq.ifr_ifindex); - - struct packet_mreq mreq = { - .mr_ifindex = ifreq.ifr_ifindex, - .mr_type = PACKET_MR_ALLMULTI - }; - - if (setsockopt(sfd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, - &mreq, sizeof(mreq)) < 0) { - error("setsockopt(PACKET_ADD_MEMBERSHIP): %m"); - goto error; - } - } - - /* can't set .sll_protocol to htons(ETH_P_IP), see comment above */ - if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - error("bind: %m"); - goto error; - } - - debug(DBG_IGMP, "init successful, using fd %i", sfd); - uring_task_init(&igmp->task, "igmp", uring_parent(), igmp_free); - uring_task_set_fd(&igmp->task, sfd); - uring_task_set_buf(&igmp->task, &igmp->tbuf); - igmp->task.saddr.addrlen = sizeof(igmp->task.saddr.ll); - uring_tbuf_recvmsg(&igmp->task, igmp_read_cb); - - cfg->igmp = igmp; - - return; - -error: - close(sfd); - xfree(igmp); -} diff --git a/igmp.h b/igmp.h deleted file mode 100644 index 80875b0..0000000 --- a/igmp.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef fooigmphfoo -#define fooigmphfoo - -void igmp_refdump(); - -void igmp_delete(); - -void igmp_init(); - -#endif diff --git a/main.c b/main.c deleted file mode 100644 index 047e70e..0000000 --- a/main.c +++ /dev/null @@ -1,740 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "main.h" -#include "signal-handler.h" -#include "uring.h" -#include "config-parser.h" -#include "server.h" -#include "server-config.h" -#include "announce.h" -#include "systemd.h" -#include "igmp.h" -#include "idle.h" -#include "ptimer.h" -#include - -/* Global */ -struct cfg *cfg = NULL; -bool exiting = false; -unsigned debug_mask = DBG_ERROR | DBG_INFO; - -/* Local */ -static bool daemonize = false; -static FILE *log_file = NULL; -static const char *log_file_path = NULL; - -#define ANSI_RED "\x1B[0;31m" -#define ANSI_GREEN "\x1B[0;32m" -#define ANSI_YELLOW "\x1B[0;33m" -#define ANSI_BLUE "\x1B[0;34m" -#define ANSI_MAGENTA "\x1B[0;35m" -#define ANSI_GREY "\x1B[0;38;5;245m" -#define ANSI_NORMAL "\x1B[0m" - -static void -msg(enum debug_lvl lvl, const char *fmt, va_list ap) -{ - static bool first = true; - static bool use_colors = false; - static bool sd_daemon = false; - const char *color; - const char *sd_lvl; - - assert_return(lvl != 0 && !empty_str(fmt) && ap); - - while (first) { - int fd; - const char *e; - - first = false; - - /* assume we're not launched by systemd when daemonized */ - if (daemonize) { - sd_daemon = false; - use_colors = false; - break; - } - - if (log_file) { - sd_daemon = false; - use_colors = false; - break; - } - - if (getenv("NO_COLOR")) { - sd_daemon = false; - use_colors = false; - break; - } - - fd = fileno(stderr); - if (fd < 0) { - /* Umm... */ - sd_daemon = true; - use_colors = false; - break; - } - - if (!isatty(fd)) { - sd_daemon = true; - use_colors = false; - break; - } - - /* systemd wouldn't normally set TERM */ - e = getenv("TERM"); - if (!e) { - sd_daemon = true; - use_colors = false; - break; - } - - if (streq(e, "dumb")) { - sd_daemon = false; - use_colors = false; - break; - } - - sd_daemon = false; - use_colors = true; - } - - switch (lvl) { - case DBG_ERROR: - sd_lvl = SD_ERR; - color = use_colors ? ANSI_RED : NULL; - break; - case DBG_VERBOSE: - sd_lvl = SD_INFO; - color = NULL; - break; - case DBG_INFO: - sd_lvl = SD_NOTICE; - color = NULL; - break; - default: - sd_lvl = SD_DEBUG; - color = use_colors ? ANSI_GREY : NULL; - break; - } - - if (sd_daemon) - fprintf(stderr, sd_lvl); - else if (color) - fprintf(stderr, color); - - vfprintf(log_file ? log_file : stderr, fmt, ap); - - if (color) - fprintf(stderr, ANSI_NORMAL); -} - -void -__debug(enum debug_lvl lvl, const char *fmt, ...) -{ - va_list ap; - - assert_return(lvl != 0 && !empty_str(fmt)); - - va_start(ap, fmt); - msg(lvl, fmt, ap); - va_end(ap); -} - -__attribute__((noreturn)) void -__die(const char *fmt, ...) -{ - va_list ap; - - if (!empty_str(fmt)) { - va_start(ap, fmt); - msg(DBG_ERROR, fmt, ap); - va_end(ap); - } else - error("fmt not set"); - - sd_notifyf(0, "STATUS=Error, shutting down"); - exit(EXIT_FAILURE); -}; - -static void -cfg_free(struct uring_task *task) -{ - struct cfg *xcfg = container_of(task, struct cfg, task); - - assert_return(task && xcfg == cfg); - - debug(DBG_SIG, "called"); - systemd_delete(cfg); - xfree(cfg->igmp_iface); - cfg->igmp_iface = NULL; - exiting = true; - /* The cfg struct is free:d in main() */ -} - -enum mcfg_keys { - MCFG_KEY_INVALID = 0, - MCFG_KEY_IGMP, - MCFG_KEY_IGMP_IFACE, - MCFG_KEY_ANN_INTERVAL, - MCFG_KEY_PROXY_CONN_INTERVAL, - MCFG_KEY_PROXY_CONN_ATTEMPTS, - MCFG_KEY_SOCKET_DEFER, - MCFG_KEY_SOCKET_FREEBIND, - MCFG_KEY_SOCKET_KEEPALIVE, - MCFG_KEY_SOCKET_IPTOS, - MCFG_KEY_SOCKET_NODELAY, -}; - -struct cfg_key_value_map mcfg_key_map[] = { - { - .key_name = "igmp", - .key_value = MCFG_KEY_IGMP, - .value_type = CFG_VAL_TYPE_BOOL, - }, { - .key_name = "igmp_iface", - .key_value = MCFG_KEY_IGMP_IFACE, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "announce_interval", - .key_value = MCFG_KEY_ANN_INTERVAL, - .value_type = CFG_VAL_TYPE_UINT16, - }, { - .key_name = "proxy_connection_interval", - .key_value = MCFG_KEY_PROXY_CONN_INTERVAL, - .value_type = CFG_VAL_TYPE_UINT16, - }, { - .key_name = "proxy_connection_attempts", - .key_value = MCFG_KEY_PROXY_CONN_ATTEMPTS, - .value_type = CFG_VAL_TYPE_UINT16, - }, { - .key_name = "socket_defer", - .key_value = MCFG_KEY_SOCKET_DEFER, - .value_type = CFG_VAL_TYPE_BOOL, - }, { - .key_name = "socket_freebind", - .key_value = MCFG_KEY_SOCKET_FREEBIND, - .value_type = CFG_VAL_TYPE_BOOL, - }, { - .key_name = "socket_keepalive", - .key_value = MCFG_KEY_SOCKET_KEEPALIVE, - .value_type = CFG_VAL_TYPE_BOOL, - }, { - .key_name = "socket_iptos", - .key_value = MCFG_KEY_SOCKET_IPTOS, - .value_type = CFG_VAL_TYPE_BOOL, - }, { - .key_name = "socket_nodelay", - .key_value = MCFG_KEY_SOCKET_NODELAY, - .value_type = CFG_VAL_TYPE_BOOL, - }, { - .key_name = NULL, - .key_value = MCFG_KEY_INVALID, - .value_type = CFG_VAL_TYPE_INVALID, - } -}; - -static void -cfg_read() -{ - FILE *cfgfile; - const char *path; - char buf[4096]; - char *pos = buf; - size_t rd = 0; - size_t r; - - assert_return(cfg); - - if (cfg->cfg_file) - path = cfg->cfg_file; - else - path = DEFAULT_MAIN_CFG_FILE; - - cfgfile = fopen(path, "re"); - if (!cfgfile) { - /* ENOENT is only an error with an explicitly set path */ - if (errno == ENOENT && !cfg->cfg_file) - return; - else if (errno == ENOENT) - die("main config file (%s) missing", path); - else - die("fopen(%s): %m", path); - } - - debug(DBG_CFG, "opened main config file (%s)", path); - - while (rd < sizeof(buf)) { - r = fread(pos, 1, sizeof(buf) - rd - 1, cfgfile); - if (r == 0) - break; - rd += r; - pos += r; - } - - if (rd == 0) - die("main config file (%s) zero size", path); - - if (rd >= sizeof(buf)) - die("main config file (%s) too large", path); - - fclose(cfgfile); - *pos = '\0'; - pos = buf; - - if (!config_parse_header(path, "mcproxy", &pos)) - die("main config file (%s) invalid", path); - - while (true) { - int key; - const char *keyname; - struct cfg_value value; - - if (!config_parse_line(path, &pos, mcfg_key_map, - &key, &keyname, &value)) - break; - - if (key == MCFG_KEY_INVALID) - die("main config file (%s) invalid", path); - - debug(DBG_CFG, "main cfg: key %s", keyname); - - switch (key) { - - case MCFG_KEY_IGMP: - cfg->do_igmp = value.boolean; - break; - - case MCFG_KEY_IGMP_IFACE: - cfg->igmp_iface = xstrdup(value.str); - if (!cfg->igmp_iface) - die("xstrdup: %m"); - - break; - - case MCFG_KEY_ANN_INTERVAL: - cfg->announce_interval = value.uint16; - break; - - case MCFG_KEY_PROXY_CONN_INTERVAL: - cfg->proxy_connection_interval = value.uint16; - break; - - case MCFG_KEY_PROXY_CONN_ATTEMPTS: - cfg->proxy_connection_attempts = value.uint16; - break; - - case MCFG_KEY_SOCKET_DEFER: - cfg->socket_defer = value.boolean; - break; - - case MCFG_KEY_SOCKET_FREEBIND: - cfg->socket_freebind = value.boolean; - break; - - case MCFG_KEY_SOCKET_KEEPALIVE: - cfg->socket_keepalive = value.boolean; - break; - - case MCFG_KEY_SOCKET_IPTOS: - cfg->socket_iptos = value.boolean; - break; - - case MCFG_KEY_SOCKET_NODELAY: - cfg->socket_nodelay = value.boolean; - break; - - case MCFG_KEY_INVALID: - default: - die("main config file (%s) invalid", path); - } - } -} - -const struct { - const char *name; - unsigned val; -} debug_category_str[] = { - { - .name = "config", - .val = DBG_CFG, - },{ - .name = "refcount", - .val = DBG_REF, - },{ - .name = "malloc", - .val = DBG_MALLOC, - },{ - .name = "announce", - .val = DBG_ANN, - },{ - .name = "signal", - .val = DBG_SIG, - },{ - .name = "uring", - .val = DBG_UR, - },{ - .name = "server", - .val = DBG_SRV, - },{ - .name = "proxy", - .val = DBG_PROXY, - },{ - .name = "rcon", - .val = DBG_RCON, - },{ - .name = "idle", - .val = DBG_IDLE, - },{ - .name = "igmp", - .val = DBG_IGMP, - },{ - .name = "systemd", - .val = DBG_SYSD, - },{ - .name = "dns", - .val = DBG_DNS, - },{ - .name = "timer", - .val = DBG_TIMER, - },{ - .name = NULL, - .val = 0, - } -}; - -__attribute__((noreturn)) static void -usage(int argc, char **argv, bool invalid) -{ - if (invalid) - info("Invalid option(s)"); - - info("Usage: %s [OPTIONS]\n" - "\n" - "Valid options:\n" - " -c, --cfgdir=DIR\tlook for configuration files in DIR\n" - " -u, --user=USER\trun as USER\n" - " -D, --daemonize\trun in daemon mode (disables stderr output)\n" - " -l, --logfile=FILE\tlog to FILE instead of stderr\n" - " -h, --help\t\tprint this information\n" - " -v, --verbose\t\tenable verbose logging\n" - " -d, --debug=CATEGORY\tenable debugging for CATEGORY\n" - "\t\t\t(use \"list\" to see available categories,\n" - "\t\t\t or \"all\" to enable all categories)\n", - argv ? argv[0] : "mcproxy"); - - exit(invalid ? EXIT_FAILURE : EXIT_SUCCESS); -} - -static void -cfg_init(int argc, char **argv) -{ - int c; - unsigned i; - - assert_die(argc > 0 && argv, "invalid arguments"); - - cfg = zmalloc(sizeof(*cfg)); - if (!cfg) - die("malloc: %m"); - - uring_task_init(&cfg->task, "main", NULL, cfg_free); - list_init(&cfg->servers); - - cfg->cfg_dir = DEFAULT_CFG_DIR; - cfg->announce_interval = DEFAULT_ANNOUNCE_INTERVAL; - cfg->proxy_connection_interval = DEFAULT_PROXY_CONN_INTERVAL; - cfg->proxy_connection_attempts = DEFAULT_PROXY_CONN_ATTEMPTS; - cfg->socket_defer = DEFAULT_SOCKET_DEFER; - cfg->socket_freebind = DEFAULT_SOCKET_FREEBIND; - cfg->socket_keepalive = DEFAULT_SOCKET_KEEPALIVE; - cfg->socket_iptos = DEFAULT_SOCKET_IPTOS; - cfg->socket_nodelay = DEFAULT_SOCKET_NODELAY; - cfg->uid = geteuid(); - cfg->gid = getegid(); - - while (true) { - int option_index = 0; - static struct option long_options[] = { - { "cfgdir", required_argument, 0, 'c' }, - { "cfgfile", required_argument, 0, 'C' }, - { "user", required_argument, 0, 'u' }, - { "daemonize", no_argument, 0, 'D' }, - { "logfile", required_argument, 0, 'l' }, - { "help", no_argument, 0, 'h' }, - { "verbose", no_argument, 0, 'v' }, - { "debug", required_argument, 0, 'd' }, - { 0, 0, 0, 0 } - }; - - c = getopt_long(argc, argv, ":c:C:u:Dl:hvd:", - long_options, &option_index); - if (c == -1) - break; - - switch (c) { - case 'c': - cfg->cfg_dir = optarg; - break; - - case 'C': - cfg->cfg_file = optarg; - break; - - case 'v': - debug_mask |= DBG_VERBOSE; - break; - - case 'D': - daemonize = true; - break; - - case 'l': - log_file_path = optarg; - break; - - case 'u': { - struct passwd *pwd; - - errno = 0; - pwd = getpwnam(optarg); - if (!pwd) { - if (errno == 0) - errno = ESRCH; - if (errno == ESRCH) - die("failed to find user %s", optarg); - else - die("failed to find user %s (%m)", optarg); - } - - debug(DBG_CFG, "asked to execute with uid %ji gid %ji", - (intmax_t)pwd->pw_uid, - (intmax_t)pwd->pw_gid); - cfg->uid = pwd->pw_uid; - cfg->gid = pwd->pw_gid; - break; - } - - case 'd': - if (strcaseeq(optarg, "all")) { - debug_mask = ~0; - break; - } else if (strcaseeq(optarg, "list")) { - info("Debug categories:"); - info(" * all"); - for (i = 0; debug_category_str[i].name; i++) - info(" * %s", debug_category_str[i].name); - exit(EXIT_FAILURE); - } - - for (i = 0; debug_category_str[i].name; i++) { - if (strcaseeq(optarg, debug_category_str[i].name)) - break; - } - - if (!debug_category_str[i].name) - usage(argc, argv, true); - - debug_mask |= DBG_VERBOSE; - debug_mask |= debug_category_str[i].val; - break; - - case 'h': - usage(argc, argv, false); - - default: - usage(argc, argv, true); - } - - } - - if (optind < argc) - usage(argc, argv, true); -} - -static void -cfg_apply() -{ - if (cfg->uid == 0 || cfg->gid == 0) - /* This catches both -u root and running as root without -u */ - die("Execution as root is not supported (use -u )"); - - capng_clear(CAPNG_SELECT_BOTH); - if (capng_updatev(CAPNG_ADD, - CAPNG_EFFECTIVE | CAPNG_PERMITTED, - CAP_NET_RAW, CAP_NET_BIND_SERVICE, -1)) - die("capng_updatev failed"); - - if (geteuid() != cfg->uid) { - if (capng_change_id(cfg->uid, - cfg->gid, - CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING)) - die("capng_change_id failed"); - } else { - /* - * This can fail if any of the caps are lacking, but it'll - * be re-checked later. - */ - capng_apply(CAPNG_SELECT_BOTH); - setgroups(0, NULL); - } - - if (daemonize) { - if (daemon(1, 0) < 0) - die("daemon() failed: %m"); - } - - if (log_file_path) { - log_file = fopen(log_file_path, "ae"); - if (!log_file) - die("fopen(%s) failed: %m", log_file_path); - } - - /* - * Do this after caps have been dropped to make sure we're not - * accessing a directory we should have permissions to. - */ - if (chdir(cfg->cfg_dir)) - die("chdir(%s): %m", cfg->cfg_dir); - - if (debug_enabled(DBG_VERBOSE)) { - char *wd; - - wd = get_current_dir_name(); - verbose("Working directory: %s", wd ? wd : ""); - free(wd); - } -} - -void -dump_tree() -{ - struct server *server; - - if (!debug_enabled(DBG_REF)) - return; - - info("\n\n"); - info("Dumping Tree"); - info("============"); - uring_task_refdump(&cfg->task); - uring_refdump(); - signal_refdump(); - ptimer_refdump(); - idle_refdump(); - igmp_refdump(); - announce_refdump(); - server_cfg_monitor_refdump(); - list_for_each_entry(server, &cfg->servers, list) - server_refdump(server); - info("============"); - info("\n\n"); -} - -int -main(int argc, char **argv) -{ - struct server *server; - unsigned server_count; - struct rlimit old_rlimit; - - cfg_init(argc, argv); - - cfg_apply(); - - cfg_read(); - - /* - * In the splice case we use 4 fds per proxy connection... - */ - if (prlimit(0, RLIMIT_NOFILE, NULL, &old_rlimit) == 0) { - struct rlimit new_rlimit; - - new_rlimit.rlim_cur = old_rlimit.rlim_max; - new_rlimit.rlim_max = old_rlimit.rlim_max; - - if (prlimit(0, RLIMIT_NOFILE, &new_rlimit, NULL) == 0) - debug(DBG_MALLOC, "prlimit(NOFILE): %u/%u -> %u/%u", - (unsigned)old_rlimit.rlim_cur, - (unsigned)old_rlimit.rlim_max, - (unsigned)new_rlimit.rlim_cur, - (unsigned)new_rlimit.rlim_cur); - } - - uring_init(); - - ptimer_init(); - - igmp_init(); - - /* Drop CAP_NET_RAW (if we have it), only used for igmp */ - capng_clear(CAPNG_SELECT_BOTH); - if (capng_update(CAPNG_ADD, - CAPNG_EFFECTIVE | CAPNG_PERMITTED, - CAP_NET_BIND_SERVICE)) - die("capng_update failed"); - - if (capng_apply(CAPNG_SELECT_BOTH)) { - /* Try clearing all caps, shouldn't fail */ - capng_clear(CAPNG_SELECT_BOTH); - if (capng_apply(CAPNG_SELECT_BOTH)) - die("capng_apply failed"); - } - - signal_init(); - - server_cfg_monitor_init(); - - announce_init(); - - if (!cfg->igmp) - announce_start(0); - - idle_init(); - - uring_task_put(&cfg->task); - - server_count = 0; - list_for_each_entry(server, &cfg->servers, list) - server_count++; - - sd_notifyf(0, "READY=1\n" - "STATUS=Running, %u server configurations loaded\n" - "MAINPID=%lu", - server_count, - (unsigned long)getpid()); - - info("mcproxy (%s) started, %u server configurations loaded", - VERSION, server_count); - - uring_event_loop(); - - verbose("Exiting"); - - xfree(cfg); - cfg = NULL; - - if (debug_enabled(DBG_MALLOC)) - debug_resource_usage(); - - fflush(stdout); - fflush(stderr); - exit(EXIT_SUCCESS); -} diff --git a/main.h b/main.h deleted file mode 100644 index 256ddae..0000000 --- a/main.h +++ /dev/null @@ -1,175 +0,0 @@ -#ifndef foomainhfoo -#define foomainhfoo - -#include -#include - -struct cfg; - -#include "utils.h" - -extern struct cfg *cfg; -extern bool exiting; -extern unsigned debug_mask; - -enum debug_lvl { - DBG_ERROR = (0x1 << 1), - DBG_INFO = (0x1 << 2), - DBG_VERBOSE = (0x1 << 3), - DBG_CFG = (0x1 << 4), - DBG_REF = (0x1 << 5), - DBG_MALLOC = (0x1 << 6), - DBG_ANN = (0x1 << 7), - DBG_SIG = (0x1 << 8), - DBG_UR = (0x1 << 9), - DBG_SRV = (0x1 << 10), - DBG_PROXY = (0x1 << 11), - DBG_RCON = (0x1 << 12), - DBG_IDLE = (0x1 << 13), - DBG_IGMP = (0x1 << 14), - DBG_SYSD = (0x1 << 15), - DBG_DNS = (0x1 << 16), - DBG_TIMER = (0x1 << 17), -}; - -static inline bool -debug_enabled(enum debug_lvl lvl) -{ - return !!(lvl & debug_mask); -} - -void __debug(enum debug_lvl lvl, const char *fmt, ...) __attribute__((format(printf, 2, 3))); - -#define __ifdebug(lvl, fmt, ...) \ - do { \ - if (debug_enabled((lvl))) \ - __debug((lvl), fmt "\n"__VA_OPT__(,) __VA_ARGS__); \ - } while (0) - -#define debug(lvl, fmt, ...) __ifdebug((lvl), "%s:%s:%i: " fmt, \ - __FILE__, __func__, __LINE__ \ - __VA_OPT__(,) __VA_ARGS__) -#define verbose(fmt, ...) __ifdebug(DBG_VERBOSE, fmt, __VA_ARGS__) -#define info(fmt, ...) __ifdebug(DBG_INFO, fmt, __VA_ARGS__) -#define error(fmt, ...) __ifdebug(DBG_ERROR, "%s:%s:%i: " fmt, \ - __FILE__, __func__, __LINE__ \ - __VA_OPT__(,) __VA_ARGS__) - -void __die(const char *fmt, ...) __attribute__((format(printf, 1, 2))); - -#define die(fmt, ...) \ - __die("%s:%s:%i: " fmt "\n", \ - __FILE__, __func__, __LINE__ \ - __VA_OPT__(,) __VA_ARGS__) - -#define assert_log(expr, msg) \ - ((expr) ? \ - (true) : \ - (__debug(DBG_ERROR, "%s:%s:%i: assertion \"" msg "\" failed\n", \ - __FILE__, __func__, __LINE__), false)) - -#define assert_return(expr, ...) \ - do { \ - if (!assert_log(expr, #expr)) \ - return __VA_ARGS__; \ - } while (0) - -#define assert_return_silent(expr, ...) \ - do { \ - if (!(expr)) \ - return __VA_ARGS__; \ - } while (0) - -#define assert_die(expr, msg) \ - do { \ - if (!assert_log(expr, #expr)) \ - die(msg); \ - } while (0) - -#define assert_task_alive_or(lvl, t, cmd) \ -do { \ - if (!(t)) { \ - error("invalid task"); \ - cmd; \ - } \ - \ - if ((t)->dead) { \ - debug((lvl), "task dead"); \ - cmd; \ - } \ -} while(0) - -#define assert_task_alive(lvl, t) assert_task_alive_or((lvl), (t), return) - -void dump_tree(); - -struct uring_task; - -/* To save typing in all the function definitions below */ -typedef void (*utask_cb_t)(struct uring_task *, int res); -typedef int (*rutask_cb_t)(struct uring_task *, int res); - -struct uring_task_buf { - char buf[4096]; - size_t len; - size_t done; - struct iovec iov; - struct msghdr msg; -}; - -struct uring_task { - const char *name; - unsigned refcount; - int fd; - struct uring_task *parent; - void (*free)(struct uring_task *); - bool dead; - struct uring_task_buf *tbuf; - - /* called once or repeatedly until is_complete_cb is satisfied */ - utask_cb_t cb; - - /* returns: 0 = not complete; < 0 = error; > 0 = complete */ - rutask_cb_t is_complete_cb; - - /* called once tbuf processing is done */ - utask_cb_t final_cb; - - /* used for recvmsg/sendmsg */ - struct saddr saddr; - void *priv; -}; - -struct cfg { - /* Options */ - uid_t uid; - gid_t gid; - const char *cfg_dir; - const char *cfg_file; - bool do_igmp; - char *igmp_iface; - bool splice_supported; - uint16_t announce_interval; - uint16_t proxy_connection_interval; - uint16_t proxy_connection_attempts; - bool socket_defer; - bool socket_freebind; - bool socket_keepalive; - bool socket_iptos; - bool socket_nodelay; - - /* Bookkeeping */ - struct uring_ev *uring; - struct server_cfg_monitor *server_cfg_monitor; - struct signal_ev *signal; - struct announce *announce; - struct ptimer *ptimer; - struct igmp *igmp; - struct idle *idle; - struct sd_bus *sd_bus; - bool sd_bus_failed; - struct uring_task task; - struct list_head servers; -}; - -#endif diff --git a/mcserverctl.c b/mcserverctl.c deleted file mode 100644 index 8a799a2..0000000 --- a/mcserverctl.c +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include -#include -#include - -#include "rcon-protocol.h" - - -int -main(int argc, char **argv) -{ - char buf[4096]; - size_t len; - int32_t id, type; - const char *msg, *error; - - fprintf(stderr, "Started\n"); - - if (!rcon_protocol_create_packet(buf, sizeof(buf), &len, - 1, RCON_PACKET_LOGIN, - "test")) { - fprintf(stderr, "Failed to create packet\n"); - exit(EXIT_FAILURE); - } - - if (!rcon_protocol_packet_complete(buf, len)) { - fprintf(stderr, "Packet not complete\n"); - exit(EXIT_FAILURE); - } - - if (!rcon_protocol_read_packet(buf, len, &id, &type, &msg, &error)) { - fprintf(stderr, "Packet parsing failed: %s\n", error); - exit(EXIT_FAILURE); - } - - fprintf(stderr, "Packet - id: %" PRIi32 ", type: %" PRIi32 ", msg: %s\n", - id, type, msg); - - exit(EXIT_SUCCESS); -} - diff --git a/mcserverctl/mcserverctl.c b/mcserverctl/mcserverctl.c new file mode 100644 index 0000000..e29dcef --- /dev/null +++ b/mcserverctl/mcserverctl.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +#include "rcon-protocol.h" + +int +main(int argc, char **argv) +{ + char buf[4096]; + size_t len; + int32_t id, type; + const char *msg, *error; + + fprintf(stderr, "Started\n"); + + if (!rcon_protocol_create_packet(buf, sizeof(buf), &len, + 1, RCON_PACKET_LOGIN, + "test")) { + fprintf(stderr, "Failed to create packet\n"); + exit(EXIT_FAILURE); + } + + if (!rcon_protocol_packet_complete(buf, len)) { + fprintf(stderr, "Packet not complete\n"); + exit(EXIT_FAILURE); + } + + if (!rcon_protocol_read_packet(buf, len, &id, &type, &msg, &error)) { + fprintf(stderr, "Packet parsing failed: %s\n", error); + exit(EXIT_FAILURE); + } + + fprintf(stderr, "Packet - id: %" PRIi32 ", type: %" PRIi32 ", msg: %s\n", + id, type, msg); + + exit(EXIT_SUCCESS); +} + diff --git a/mcserverctl/meson.build b/mcserverctl/meson.build new file mode 100644 index 0000000..4e960b7 --- /dev/null +++ b/mcserverctl/meson.build @@ -0,0 +1,14 @@ +mcserverctl_sources = [ + 'mcserverctl.c', +] + +mcserverctl_deps = [ + dep_libshared, +] + +executable( + 'mcserverctl', + mcserverctl_sources, + dependencies: mcserverctl_deps, +) + diff --git a/mcserverproxy/announce.c b/mcserverproxy/announce.c new file mode 100644 index 0000000..13ef423 --- /dev/null +++ b/mcserverproxy/announce.c @@ -0,0 +1,116 @@ +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "uring.h" +#include "announce.h" +#include "server.h" +#include "ptimer.h" + +struct announce { + uint64_t value; + struct uring_task task; + struct ptimer_task ptask; + int mcast_fd; +}; + +static void +announce_cb(struct ptimer_task *ptask) +{ + struct announce *announce = container_of(ptask, struct announce, ptask); + struct server *server; + + assert_return(ptask); + assert_task_alive(DBG_ANN, &announce->task); + + debug(DBG_ANN, "announcing servers"); + list_for_each_entry(server, &cfg->servers, list) + server_announce(server, announce->mcast_fd); +} + +static void +announce_free(struct uring_task *task) +{ + struct announce *announce = container_of(task, struct announce, task); + + assert_return(task); + debug(DBG_ANN, "task %p, announce 0x%p, mcast_fd: %i", + task, announce, announce->mcast_fd); + close(announce->mcast_fd); + xfree(announce); +} + +void +announce_refdump() +{ + assert_return_silent(cfg->announce); + + uring_task_refdump(&cfg->announce->task); +} + +void +announce_delete() +{ + assert_return_silent(cfg->announce); + + debug(DBG_ANN, "called"); + announce_stop(); + uring_task_destroy(&cfg->announce->task); + cfg->announce = NULL; +} + +void +announce_stop() +{ + struct announce *announce = cfg->announce; + + assert_return_silent(announce); + + ptimer_del_task(&announce->ptask); +} + +void +announce_start(unsigned duration) +{ + struct announce *announce = cfg->announce; + unsigned times; + + assert_return_silent(announce); + + if (duration == 0) + times = 0; + else + times = MAX(announce->ptask.times, + DIV_ROUND_UP(duration, cfg->announce_interval)); + + announce->ptask.times = times; + ptimer_add_task(&announce->ptask); +} + +void +announce_init() +{ + struct announce *announce; + int sfd; + + assert_return(!cfg->announce); + assert_return_silent(cfg->announce_interval > 0); + + announce = zmalloc(sizeof(*announce)); + if (!announce) + die("malloc: %m"); + + sfd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (sfd < 0) + die("socket: %m"); + + uring_task_init(&announce->task, "announce", uring_parent(), announce_free); + ptask_init(&announce->ptask, cfg->announce_interval, 0, announce_cb); + announce->mcast_fd = sfd; + cfg->announce = announce; +} + diff --git a/mcserverproxy/announce.h b/mcserverproxy/announce.h new file mode 100644 index 0000000..77a36f2 --- /dev/null +++ b/mcserverproxy/announce.h @@ -0,0 +1,14 @@ +#ifndef fooannouncehfoo +#define fooannouncehfoo + +void announce_refdump(); + +void announce_delete(); + +void announce_stop(); + +void announce_start(unsigned duration); + +void announce_init(); + +#endif diff --git a/mcserverproxy/config-parser.c b/mcserverproxy/config-parser.c new file mode 100644 index 0000000..ffed7f1 --- /dev/null +++ b/mcserverproxy/config-parser.c @@ -0,0 +1,490 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "config-parser.h" + +static void +eat_whitespace_and_comments(char **pos) +{ + assert_return(pos && *pos); + + while (true) { + while (isspace(**pos)) + (*pos)++; + + if (**pos != '#') + return; + + while (**pos != '\r' && **pos != '\n' && **pos != '\0') + (*pos)++; + } +} + +static char * +get_line(char **pos) +{ + char *begin, *end; + + assert_return(pos && *pos, NULL); + + begin = *pos; + while (isspace(*begin)) + begin++; + + if (*begin == '\0') + return NULL; + + end = begin; + while (*end != '\n' && *end != '\0') + end++; + + if (*end == '\0') + *pos = end; + else + *pos = end + 1; + + while (isspace(*end)) { + *end = '\0'; + end--; + } + + return begin; +} + +static bool +dnslookup(const char *name, uint16_t port, struct cfg_value *rvalue, bool async) +{ + struct sockaddr_in *in4; + struct sockaddr_in6 *in6; + struct dns_async tmp; + struct dns_async *dns; + int mode = async ? GAI_NOWAIT : GAI_WAIT; + struct addrinfo *results = NULL, *ai; + struct saddr *saddr = NULL; + bool rv = false; + int r; + + assert_return(!empty_str(name) && strlen(name) < sizeof(dns->name) && port > 0 && rvalue, false); + + if (async) { + rvalue->type = CFG_VAL_TYPE_ASYNC_ADDRS; + rvalue->dns_async = NULL; + dns = zmalloc(sizeof(*dns)); + if (!dns) { + error("async DNS lookup of %s failed: %m", name); + goto out; + } + debug(DBG_DNS, "doing async DNS lookup of %s: %p", name, dns); + } else { + memset(&tmp, 0, sizeof(tmp)); + dns = &tmp; + debug(DBG_DNS, "doing sync DNS lookup of %s", name); + } + + sprintf(dns->name, "%s", name); + sprintf(dns->port, "%" PRIu16, port); + + dns->req.ai_family = AF_UNSPEC; + dns->req.ai_socktype = SOCK_STREAM; + dns->req.ai_protocol = 0; + dns->req.ai_flags = AI_NUMERICSERV; + + dns->sev.sigev_notify = SIGEV_SIGNAL; + dns->sev.sigev_signo = SIGUSR1; + dns->sev.sigev_value.sival_ptr = dns; + + dns->gcb.ar_name = dns->name; + dns->gcb.ar_service = dns->port; + dns->gcb.ar_request = &dns->req; + + struct gaicb *gcbs[] = { &dns->gcb }; + + r = getaddrinfo_a(mode, gcbs, ARRAY_SIZE(gcbs), &dns->sev); + if (r != 0) { + error("getaddrinfo(%s:%" PRIu16 "): %s", name, port, gai_strerror(r)); + goto out; + } + + if (async) { + rvalue->dns_async = dns; + rv = true; + goto out; + } + + results = dns->gcb.ar_result; + + for (ai = results; ai; ai = ai->ai_next) { + saddr = zmalloc(sizeof(*saddr)); + if (!saddr) { + error("sync DNS lookup of %s failed: %m", name); + 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); + error("addrstr: %s", saddr->addrstr); + list_add(&saddr->list, &rvalue->saddrs); + break; + + case AF_INET6: + in6 = (struct sockaddr_in6 *)ai->ai_addr; + saddr_set_ipv6(saddr, &in6->sin6_addr, in6->sin6_port); + error("addrstr: %s", saddr->addrstr); + list_add(&saddr->list, &rvalue->saddrs); + break; + + default: + error("getaddrinfo(%s:%s): unknown address family (%i)", + dns->name, dns->port, ai->ai_family); + xfree(saddr); + break; + } + } + + rv = true; + +out: + freeaddrinfo(results); + return rv; +} + +static bool +strtosockaddrs(const char *str, struct cfg_value *rvalue, bool async) +{ + struct saddr *saddr; + uint16_t port; + char *tmp; + struct list_head *list; + unsigned naddrs = 0; + + assert_return(!empty_str(str) && rvalue, false); + + rvalue->type = CFG_VAL_TYPE_ADDRS; + list = &rvalue->saddrs; + list_init(list); + + if (*str == '[') { + /* IPv6, [a:b:c...h]:p or [*]:p */ + debug(DBG_CFG, "attempting to parse IPv6 addr (%s)", str); + + str++; + tmp = strchr(str, ']'); + if (!tmp) + goto error; + *tmp = '\0'; + + saddr = zmalloc(sizeof(*saddr)); + if (!saddr) + goto error; + + /* early list_add to make sure saddr is free():d on error */ + list_add(&saddr->list, list); + + if (streq(str, "*")) + saddr->in6.sin6_addr = in6addr_any; + else if (inet_pton(AF_INET6, str, &saddr->in6.sin6_addr) <= 0) + goto error; + + tmp++; + if (*tmp != ':') + goto error; + + tmp++; + if (strtou16_strict(tmp, &port) < 0) + goto error; + + saddr_set_ipv6(saddr, NULL, htons(port)); + naddrs++; + + } else if (*str == '*') { + /* IPv4, *:p */ + debug(DBG_CFG, "attempting to parse IPv4 addr (%s)", str); + + str++; + if (*str != ':') + goto error; + + str++; + if (strtou16_strict(str, &port) < 0) + goto error; + + saddr = zmalloc(sizeof(*saddr)); + if (!saddr) + goto error; + + saddr_set_ipv4(saddr, INADDR_ANY, htons(port)); + list_add(&saddr->list, list); + naddrs++; + + } else if ((tmp = strchr(str, ':'))) { + /* IPv4, a.b.c.d:p or IPv4/6 hostname:p */ + debug(DBG_CFG, "attempting to parse IPv4 addr or hostname (%s)", str); + + *tmp = '\0'; + tmp++; + if (strtou16_strict(tmp, &port) < 0) + goto error; + + saddr = zmalloc(sizeof(*saddr)); + if (!saddr) + goto error; + + if (inet_pton(AF_INET, str, &saddr->in4.sin_addr) > 0) { + debug(DBG_CFG, "got an IPv4:port (%s:%" PRIu16 ")", str, port); + saddr_set_ipv4(saddr, saddr->in4.sin_addr.s_addr, htons(port)); + list_add(&saddr->list, list); + naddrs++; + goto success; + } + + xfree(saddr); + debug(DBG_CFG, "maybe got a hostname:port (%s:%" PRIu16 ")", str, port); + if (!dnslookup(str, port, rvalue, async)) + goto error; + + } else if (strtou16_strict(tmp, &port) == 0) { + /* Port */ + debug(DBG_CFG, "attempting to parse a port number (%s)", str); + + saddr = zmalloc(sizeof(*saddr)); + if (!saddr) + goto error; + + saddr_set_ipv6(saddr, &in6addr_any, htons(port)); + list_add(&saddr->list, list); + naddrs++; + + saddr = zmalloc(sizeof(*saddr)); + if (!saddr) + goto error; + + saddr_set_ipv4(saddr, INADDR_ANY, htons(port)); + list_add(&saddr->list, list); + naddrs++; + + } else { + /* Unknown */ + error("unable to parse address: %s", str); + goto error; + } + +success: + switch (rvalue->type) { + case CFG_VAL_TYPE_ADDRS: + if (list_empty(list) || naddrs == 0) { + error("empty address list"); + return false; + } + + debug(DBG_CFG, "parsed to %u addresses", naddrs); + return true; + + case CFG_VAL_TYPE_ASYNC_ADDRS: + debug(DBG_CFG, "looking up address asynchronously"); + return true; + + default: + error("invalid rvalue type"); + rvalue->type = CFG_VAL_TYPE_INVALID; + break; + } + +error: + if (rvalue->type == CFG_VAL_TYPE_ADDRS && !list_empty(list)) { + struct saddr *tmp; + + list_for_each_entry_safe(saddr, tmp, list, list) { + list_del(&saddr->list); + xfree(saddr); + } + } + return false; +} + +/* Returns true if there's data left to parse in buf */ +bool +config_parse_line(const char *filename, char **buf, + struct cfg_key_value_map *kvmap, int *rkey, + const char **rkeyname, struct cfg_value *rvalue) +{ + char *line, *tmp, *key; + int i; + + assert_return(buf && *buf && kvmap && rkey && rkeyname && rvalue, false); + + eat_whitespace_and_comments(buf); + line = get_line(buf); + if (!line) + return false; + + debug(DBG_CFG, "%s: parsing config line: %s", filename, line); + + tmp = line; + while (isspace(*tmp)) + tmp++; + + if (*tmp == '\0') + goto error; + + key = tmp; + while (*tmp != '\0' && !isspace(*tmp)) + tmp++; + + if (*tmp == '\0') + goto error; + + *tmp = '\0'; + tmp++; + + while (isspace(*tmp)) + tmp++; + + if (*tmp != '=') + goto error; + + tmp++; + while (isspace(*tmp)) + tmp++; + + if (*tmp == '\0') + goto error; + + for (i = 0; kvmap[i].key_name; i++) { + if (!streq(kvmap[i].key_name, key)) + continue; + + switch (kvmap[i].value_type) { + + case CFG_VAL_TYPE_STRING: + rvalue->type = CFG_VAL_TYPE_STRING; + rvalue->str = tmp; + break; + + case CFG_VAL_TYPE_UINT16: { + uint16_t v; + + if (strtou16_strict(tmp, &v) < 0) + goto error; + + rvalue->type = CFG_VAL_TYPE_UINT16; + rvalue->uint16 = v; + break; + } + + case CFG_VAL_TYPE_ADDRS: + if (!strtosockaddrs(tmp, rvalue, false)) + goto error; + + if (rvalue->type != CFG_VAL_TYPE_ADDRS) { + error("invalid type returned from strtosockaddrs"); + goto error; + } + + if (list_empty(&rvalue->saddrs)) { + error("empty address list"); + goto error; + } + break; + + case CFG_VAL_TYPE_ASYNC_ADDRS: + if (!strtosockaddrs(tmp, rvalue, true)) + goto error; + + switch (rvalue->type) { + case CFG_VAL_TYPE_ADDRS: + if (list_empty(&rvalue->saddrs)) { + error("empty address list"); + goto error; + } + break; + + case CFG_VAL_TYPE_ASYNC_ADDRS: + if (!rvalue->dns_async) { + error("dns_async not set"); + goto error; + } + break; + + default: + error("invalid type returned from strtosockaddrs"); + goto error; + } + + break; + + case CFG_VAL_TYPE_BOOL: + if (strcaseeq(tmp, "yes") || strcaseeq(tmp, "true")) { + rvalue->type = CFG_VAL_TYPE_BOOL; + rvalue->boolean = true; + } else if (strcaseeq(tmp, "no") || strcaseeq(tmp, "false")) { + rvalue->type = CFG_VAL_TYPE_BOOL; + rvalue->boolean = false; + } else { + error("invalid boolean value (%s)", tmp); + goto error; + } + break; + + case CFG_VAL_TYPE_INVALID: + /* fall through */ + default: + goto error; + } + + /* sanity check */ + if ((rvalue->type != kvmap[i].value_type) && + ((kvmap[i].value_type != CFG_VAL_TYPE_ASYNC_ADDRS) && + (rvalue->type != CFG_VAL_TYPE_ADDRS))) { + error("rvalue->type != kvmap->type"); + goto error; + } + + *rkey = kvmap[i].key_value; + *rkeyname = kvmap[i].key_name; + return true; + } + +error: + /* FIXME: the line is already mangled here, a line number would be nice */ + error("%s: invalid config line: %s", filename, line); + rvalue->type = CFG_VAL_TYPE_INVALID; + *rkey = 0; + *rkeyname = NULL; + return true; +} + +bool +config_parse_header(const char *filename, const char *title, char **buf) +{ + char *line; + + assert_return(!empty_str(filename) && !empty_str(title) && buf && *buf, false); + + eat_whitespace_and_comments(buf); + + line = get_line(buf); + if (!line) { + error("%s: missing header in configuration file", filename); + return false; + } else { + char titlehdr[strlen(title) + 3]; + + sprintf(titlehdr, "[%s]", title); + if (!streq(line, titlehdr)) { + error("%s: incorrect header in configuration file", filename); + return false; + } + } + + return true; +} diff --git a/mcserverproxy/config-parser.h b/mcserverproxy/config-parser.h new file mode 100644 index 0000000..3a117a3 --- /dev/null +++ b/mcserverproxy/config-parser.h @@ -0,0 +1,59 @@ +#ifndef fooconfigparserhfoo +#define fooconfigparserhfoo + +#define _GNU_SOURCE +#include +#include +#include +#include + +enum cfg_value_type { + CFG_VAL_TYPE_INVALID, + CFG_VAL_TYPE_STRING, + CFG_VAL_TYPE_UINT16, + CFG_VAL_TYPE_ADDRS, + CFG_VAL_TYPE_ASYNC_ADDRS, + CFG_VAL_TYPE_BOOL, +}; + +struct dns_async; + +typedef void (dns_cb_t)(struct dns_async *); + +struct dns_async { + char name[FQDN_STR_LEN + 1]; + char port[PORT_STR_LEN + 1]; + struct addrinfo req; + struct gaicb gcb; + struct sigevent sev; + dns_cb_t *cb; + void *priv; + struct list_head list; +}; + +struct cfg_key_value_map { + const char *key_name; + int key_value; + enum cfg_value_type value_type; +}; + +struct cfg_value { + enum cfg_value_type type; + union { + const char *str; + uint16_t uint16; + struct list_head saddrs; + struct dns_async *dns_async; + bool boolean; + }; +}; + +bool config_parse_line(const char *filename, char **buf, + struct cfg_key_value_map *kvmap, + int *rkey, const char **rkeyname, + struct cfg_value *rvalue); + +bool config_parse_header(const char *filename, + const char *title, char **buf); + +#endif diff --git a/mcserverproxy/idle.c b/mcserverproxy/idle.c new file mode 100644 index 0000000..c49846d --- /dev/null +++ b/mcserverproxy/idle.c @@ -0,0 +1,378 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "uring.h" +#include "server.h" +#include "idle.h" +#include "ptimer.h" + +struct idle { + struct ptimer_task ptask; + struct uring_task task; +}; + +static inline void +write_byte(char **pos, char byte) +{ + assert_return(pos && *pos); + + **pos = byte; + (*pos)++; +} + +#define MC_HELO 0x00 +#define MC_NEXT_STATE_STATUS 0x01 +#define MC_GET_STATUS 0x00 +#define MC_VARINT_MAX_BYTES 5 +#define MC_STATUS_REPLY 0x00 +#define MC_UNDEFINED_VERSION -1 + +static inline void +write_varint(char **pos, int32_t orig) +{ + assert_return(pos && *pos); + + uint32_t val = (uint32_t)orig; + + while (val) { + **pos = val & 0x7f; + val >>= 7; + if (val > 0) + **pos |= 0x80; + (*pos)++; + } +} + +/* + * return value: + * positive = varint parsed + * zero = need more bytes + * negative = error + */ +static inline int +read_varint(char **pos, size_t *remain, int32_t *res) +{ + unsigned consumed; + uint32_t val = 0; + + assert_return(pos && *pos && remain && res, -1); + + for (consumed = 1; consumed <= *remain; consumed++) { + uint32_t tmp; + + tmp = **pos & 0x7f; + val += (tmp << (7 * (consumed - 1))); + (*pos)++; + + if (!(tmp & 0x80)) + break; + } + + if (consumed > *remain) + return 0; + else if (consumed > MC_VARINT_MAX_BYTES) + return -1; + + *remain -= consumed; + *res = (int32_t)val; + return 1; +} + +static inline void +write_bytes(char **pos, const char *bytes, size_t n) +{ + assert_return(pos && *pos && bytes && n > 0); + + memcpy(*pos, bytes, n); + *pos += n; +} + +static inline void +write_str(char **pos, const char *str) +{ + size_t len; + + assert_return(pos && *pos && !empty_str(str)); + + len = strlen(str); + write_varint(pos, len); + write_bytes(pos, str, len); +} + +static inline void +write_cmd(char **pos, const char *begin, const char *end) +{ + assert_return(pos && *pos && begin && end && end > begin); + + write_varint(pos, end - begin); + write_bytes(pos, begin, end - begin); +} + +static int +idle_check_handshake_complete(struct uring_task *task, int res) +{ + size_t remain; + char *pos; + int32_t mclen; + int r; + + assert_return(task, -EINVAL); + assert_task_alive_or(DBG_IDLE, task, return -EINTR); + + remain = task->tbuf->len; + pos = task->tbuf->buf; + + r = read_varint(&pos, &remain, &mclen); + if (r < 0) { + error("failed to parse message length"); + return -EINVAL; + } else if (r == 0) { + return 0; + } else if (mclen < 2) { + error("short MC message"); + return -EINVAL; + } + + if (mclen < remain) { + debug(DBG_IDLE, "short MC message - len: %" PRIi32 ", remain: %zu", + mclen, remain); + return 0; + } + + debug(DBG_IDLE, "Complete message"); + return 1; +} + +#define ONLINE_NEEDLE "\"online\"" +static int +get_player_count(const char *pos, size_t remain) +{ + /* + * Example JSON (line breaks added): + * {"description":{ + * "text":"A Minecraft Server"}, + * "players":{"max":20,"online":0}, + * "version":{"name":"1.15.2","protocol":578} + * } + */ + char *online; + char *end; + unsigned count; + + assert_return(pos && remain > 0, -1); + + online = memmem(pos, remain, ONLINE_NEEDLE, strlen(ONLINE_NEEDLE)); + if (!online) { + error("could not find online count in JSON"); + return -1; + } + + remain -= (online - pos); + + end = memchr(online, '}', remain); + if (!end) { + error("could not parse JSON (no end)"); + return -1; + } + *end = '\0'; + + if (sscanf(online, ONLINE_NEEDLE " : %u", &count) != 1) { + error("could not parse JSON (online count)"); + return -1; + } + + return count; +} + +static void +idle_check_handshake_reply(struct uring_task *task, int res) +{ + struct server *server = container_of(task, struct server, idle_task); + int32_t mclen; + int32_t jsonlen; + char *pos; + size_t remain; + int player_count = -1; + int r; + + assert_return(task); + assert_task_alive(DBG_IDLE, task); + + debug(DBG_IDLE, "res: %i", res); + if (res < 0) + goto out; + + /* + fprintf(stderr, "Received MC message (%i bytes):", res); + for (int i = 0; i < res; i++) + fprintf(stderr, "0x%02hhx ", idle->remotebuf[i]); + fprintf(stderr, "n"); + */ + + remain = server->idle_buf.len; + pos = server->idle_buf.buf; + + r = read_varint(&pos, &remain, &mclen); + if (r <= 0 || mclen < 2 || mclen < remain) { + /* Should not happen since the msg has been checked already */ + error("invalid message"); + goto out; + } + + debug(DBG_IDLE, "MC message - len: %" PRIi32 ", remain: %zu", + mclen, remain); + + if (*pos != MC_STATUS_REPLY) { + error("unknown server reply (0x%02hhx)", *pos); + goto out; + } + + pos++; + remain--; + + r = read_varint(&pos, &remain, &jsonlen); + if (r <= 0) { + error("could not read JSON length"); + goto out; + } + + debug(DBG_IDLE, "MC - json len: %" PRIi32 ", remain: %zu", + jsonlen, remain); + + if (jsonlen < remain) { + error("invalid JSON length"); + goto out; + } + + /* + fprintf(stderr, "JSON: "); + for (int i = 0; i < jsonlen; i++) + fprintf(stderr, "%c", pos[i]); + */ + + player_count = get_player_count(pos, remain); + +out: + uring_task_close_fd(task); + server_set_active_players(server, player_count); + return; +} + +static void +idle_check_handshake_sent(struct uring_task *task, int res) +{ + assert_return(task); + assert_task_alive(DBG_IDLE, task); + + debug(DBG_IDLE, "sent %i bytes", res); + if (res < 0) { + uring_task_close_fd(task); + return; + } + + uring_tbuf_read_until(task, + idle_check_handshake_complete, + idle_check_handshake_reply); +} + +void +idle_check_get_player_count(struct server *server, struct connection *conn) +{ + char buf[1024]; + char *pos; + char *cmdbuf = server->idle_buf.buf; + uint16_t port; + char hostname[INET6_ADDRSTRLEN]; + + assert_return(server && conn && server->idle_task.priv); + + port = saddr_port(&conn->remote); + saddr_addr(&conn->remote, hostname, sizeof(hostname)); + + pos = buf; + write_byte(&pos, MC_HELO); + write_varint(&pos, MC_UNDEFINED_VERSION); + write_str(&pos, hostname); + write_byte(&pos, (port >> 8) & 0xff); + write_byte(&pos, (port >> 0) & 0xff); + write_byte(&pos, MC_NEXT_STATE_STATUS); + write_cmd(&cmdbuf, buf, pos); + + pos = buf; + write_byte(&pos, MC_GET_STATUS); + write_cmd(&cmdbuf, buf, pos); + + server->idle_buf.len = (cmdbuf - server->idle_buf.buf); + debug(DBG_IDLE, "sending MC message (%zu bytes)", server->idle_buf.len); + + uring_tbuf_write(&server->idle_task, idle_check_handshake_sent); +} + +static void +idle_cb(struct ptimer_task *ptask) +{ + struct idle *idle = container_of(ptask, struct idle, ptask); + struct server *server; + + assert_return(ptask); + assert_task_alive(DBG_IDLE, &idle->task); + + debug(DBG_IDLE, "timer fired"); + + list_for_each_entry(server, &cfg->servers, list) + server_idle_check(server); +} + +static void +idle_free(struct uring_task *task) +{ + struct idle *idle = container_of(task, struct idle, task); + + assert_return(task); + debug(DBG_IDLE, "task %p, idle %p", task, idle); + xfree(idle); +} + +void +idle_refdump() +{ + assert_return_silent(cfg->idle); + + uring_task_refdump(&cfg->idle->task); +} + +void +idle_delete() +{ + assert_return(cfg->idle); + + debug(DBG_IDLE, "closing fd %i", cfg->idle->task.fd); + ptimer_del_task(&cfg->idle->ptask); + uring_task_destroy(&cfg->idle->task); + cfg->idle = NULL; +} + +void +idle_init() +{ + struct idle *idle; + + assert_return(!cfg->idle); + + idle = zmalloc(sizeof(*idle)); + if (!idle) + die("malloc: %m"); + + ptask_init(&idle->ptask, 60, 0, idle_cb); + uring_task_init(&idle->task, "idle", uring_parent(), idle_free); + ptimer_add_task(&idle->ptask); + cfg->idle = idle; +} + diff --git a/mcserverproxy/idle.h b/mcserverproxy/idle.h new file mode 100644 index 0000000..d7e4ab0 --- /dev/null +++ b/mcserverproxy/idle.h @@ -0,0 +1,13 @@ +#ifndef fooidlehfoo +#define fooidlehfoo + +void idle_check_get_player_count(struct server *server, + struct connection *conn); + +void idle_refdump(); + +void idle_delete(); + +void idle_init(); + +#endif diff --git a/mcserverproxy/igmp.c b/mcserverproxy/igmp.c new file mode 100644 index 0000000..dc43a9f --- /dev/null +++ b/mcserverproxy/igmp.c @@ -0,0 +1,587 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "uring.h" +#include "igmp.h" +#include "announce.h" + +struct igmp { + struct uring_task task; + struct uring_task_buf tbuf; +}; + +#define ETH_HDR_LEN 14 +#define IPV4_MIN_HDR_LEN 20 +#define IGMP_MIN_LEN 8 + +struct __attribute__((packed, scalar_storage_order("big-endian"))) ipv4_hdr { + unsigned version:4; + unsigned ihl:4; + unsigned dscp:6; + unsigned ecn:2; + unsigned length:16; + unsigned id:16; + unsigned flags:3; + unsigned fragment_offset:13; + unsigned ttl:8; + unsigned protocol:8; + unsigned checksum:16; + unsigned src:32; + unsigned dst:32; + unsigned options[]; +}; + +enum igmp_type { + IGMP_MEMBERSHIP_QUERY = 0x11, + IGMP_V1_MEMBERSHIP_REPORT = 0x12, + IGMP_V2_MEMBERSHIP_REPORT = 0x16, + IGMP_V3_MEMBERSHIP_REPORT = 0x22, + IGMP_V2_LEAVE_GROUP = 0x17 +}; + +union igmp_msg { + struct __attribute__((packed, scalar_storage_order("big-endian"))) { + enum igmp_type type:8; + unsigned unknown:8; + unsigned checksum:16; + } common; + + struct __attribute__((packed, scalar_storage_order("big-endian"))) { + enum igmp_type type:8; + unsigned resptime:8; + unsigned checksum:16; + unsigned addr:32; + } v2; + + struct __attribute__((packed, scalar_storage_order("big-endian"))) { + enum igmp_type type:8; + unsigned reserved1:8; + unsigned checksum:16; + unsigned reserved2:16; + unsigned nrecs:16; + uint8_t records[]; + } v3; +}; + +enum igmp_v3_record_type { + IGMP_V3_REC_MODE_IS_INCL = 1, + IGMP_V3_REC_MODE_IS_EXCL = 2, + IGMP_V3_REC_MODE_CH_INCL = 3, + IGMP_V3_REC_MODE_CH_EXCL = 4 +}; + +union igmp_v3_record { + struct __attribute__((packed, scalar_storage_order("big-endian"))) { + enum igmp_v3_record_type type:8; + unsigned auxlen:8; + unsigned nsrcs:16; + unsigned addr:32; + uint32_t saddr[]; + }; +}; + +static inline unsigned short +from32to16(unsigned int x) +{ + /* add up 16-bit and 16-bit for 16+c bit */ + x = (x & 0xffff) + (x >> 16); + /* add up carry.. */ + x = (x & 0xffff) + (x >> 16); + return x; +} + +static unsigned int +do_csum(const unsigned char *buf, int len) +{ + int odd; + unsigned int result = 0; + + assert_return(buf && len > 0, 0); + + odd = 1 & (unsigned long)buf; + if (odd) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + result += (*buf << 8); +#else + result = *buf; +#endif + len--; + buf++; + } + + if (len >= 2) { + if (2 & (unsigned long)buf) { + result += *(unsigned short *)buf; + len -= 2; + buf += 2; + } + if (len >= 4) { + const unsigned char *end = buf + ((unsigned)len & ~3); + unsigned int carry = 0; + do { + unsigned int w = *(unsigned int *)buf; + buf += 4; + result += carry; + result += w; + carry = (w > result); + } while (buf < end); + result += carry; + result = (result & 0xffff) + (result >> 16); + } + if (len & 2) { + result += *(unsigned short *)buf; + buf += 2; + } + } + + if (len & 1) +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + result += *buf; +#else + result += (*buf << 8); +#endif + + result = from32to16(result); + if (odd) + result = ((result >> 8) & 0xff) | ((result & 0xff) << 8); + + return result; +} + +static inline bool +csum_valid(const char *buf, size_t len) +{ + assert_return(buf && len > 0, false); + + return do_csum((unsigned const char *)buf, len) == 0xffff; +} + +static void +igmp_match() +{ + debug(DBG_IGMP, "multicast request discovered"); + /* + * IGMP messages are sent with approx 120-130 sec intervals, + * so set time to 5 minutes to allow some slack. + */ + announce_start(5 * 60); +} + +static void +igmp_parse(struct igmp *igmp) +{ + char *buf; + size_t len; + struct ipv4_hdr *hdr; + size_t body_len; + union igmp_msg *igmp_msg; + + assert_return(igmp); + + buf = igmp->task.tbuf->buf; + len = igmp->task.tbuf->len; + hdr = (struct ipv4_hdr *)buf; + + if (len <= IPV4_MIN_HDR_LEN) + return; + + if (hdr->version != 4) + return; + + if (hdr->ihl * 4 < IPV4_MIN_HDR_LEN) + return; + + if (hdr->length < hdr->ihl * 4) + return; + + if (hdr->length != len) + return; + + if (hdr->fragment_offset > 0) + return; + + if (hdr->protocol != IPPROTO_IGMP) + return; + + if (!csum_valid(buf, hdr->ihl * 4)) + return; + + body_len = hdr->length - hdr->ihl * 4; + igmp_msg = (union igmp_msg *)(buf + hdr->ihl * 4); + + if (body_len < IGMP_MIN_LEN) + return; + + switch (igmp_msg->common.type) { + case IGMP_V1_MEMBERSHIP_REPORT: + debug(DBG_IGMP, "igmp_v1_membership_report"); + /* fall through */ + + case IGMP_V2_MEMBERSHIP_REPORT: { + struct in_addr src; + char src_str[INET_ADDRSTRLEN]; + struct in_addr dst; + char dst_str[INET_ADDRSTRLEN]; + struct in_addr grp; + char grp_str[INET_ADDRSTRLEN]; + + src.s_addr = htonl(hdr->src); + inet_ntop(AF_INET, &src, src_str, sizeof(src_str)); + dst.s_addr = htonl(hdr->dst); + inet_ntop(AF_INET, &dst, dst_str, sizeof(dst_str)); + grp.s_addr = htonl(igmp_msg->v2.addr); + inet_ntop(AF_INET, &grp, grp_str, sizeof(grp_str)); + + debug(DBG_IGMP, "igmp_v2_membership_report %s -> %s (%s)", + src_str, dst_str, grp_str); + + if (body_len != IGMP_MIN_LEN) { + error("IGMPv2 invalid size"); + break; + } + + if (!csum_valid((char *)igmp_msg, body_len)) { + error("IGMPv2 invalid checksum"); + break; + } + + debug(DBG_IGMP, "Inet addr: 0x%x", inet_addr("224.0.2.60")); + debug(DBG_IGMP, "Inet addr: 0x%x", cinet_addr(224,0,2,60)); + debug(DBG_IGMP, "Inet addr: 0x%x", chtobe32(cinet_addr(224,0,2,60))); + if (htonl(hdr->dst) != cinet_addr(224,0,2,60)) { + debug(DBG_IGMP, "IGMPv2 invalid dst addr"); + break; + } + + if (htonl(igmp_msg->v2.addr) != cinet_addr(224,0,2,60)) { + debug(DBG_IGMP, "IGMPv2 invalid grp addr"); + break; + } + + igmp_match(); + break; + } + + case IGMP_V3_MEMBERSHIP_REPORT: { + char *pos = (char *)igmp_msg; + struct in_addr src; + char src_str[INET_ADDRSTRLEN]; + struct in_addr dst; + char dst_str[INET_ADDRSTRLEN]; + + src.s_addr = htonl(hdr->src); + inet_ntop(AF_INET, &src, src_str, sizeof(src_str)); + dst.s_addr = htonl(hdr->dst); + inet_ntop(AF_INET, &dst, dst_str, sizeof(dst_str)); + + debug(DBG_IGMP, "igmp_v3_membership_report %s -> %s", + src_str, dst_str); + + debug(DBG_IGMP, "IGMPv3" + " type: %x," + " reserved: %u," + " csum: %u," + " reserved: %u," + " nrecs: %u," + " size: %zu\n", + igmp_msg->v3.type, + igmp_msg->v3.reserved1, + igmp_msg->v3.checksum, + igmp_msg->v3.reserved2, + igmp_msg->v3.nrecs, + sizeof(igmp_msg->v3)); + + if (!csum_valid(pos, body_len)) { + error("IGMPv3 csum invalid"); + break; + } + + if (htonl(hdr->dst) != cinet_addr(224,0,0,22)) { + debug(DBG_IGMP, "IGMPv2 invalid dst addr"); + break; + } + + body_len -= sizeof(igmp_msg->v3); + pos += sizeof(igmp_msg->v3); + + for (unsigned rec = 0; rec < igmp_msg->v3.nrecs; rec++) { + union igmp_v3_record *record = (union igmp_v3_record *)pos; + struct in_addr grp; + char grp_str[INET_ADDRSTRLEN]; + + if (body_len < sizeof(*record)) { + error("IGMPv3 too short"); + break; + } + + grp.s_addr = htonl(record->addr); + inet_ntop(AF_INET, &grp, grp_str, sizeof(grp_str)); + debug(DBG_IGMP, "received IGMPv3 record to %s", grp_str); + + debug(DBG_IGMP, "IGMPv3 rec, " + " type: %u," + " auxlen: %u," + " nsrcs: %u," + " addr: %s," + " size: %zu bytes", + record->type, + record->auxlen, + record->nsrcs, + grp_str, + sizeof(*record)); + + body_len -= sizeof(*record); + pos += sizeof(*record); + + if (body_len < record->nsrcs * sizeof(uint32_t) + record->auxlen) { + error("IGMPv3 too short"); + break; + } + + for (unsigned addr = 0; addr < record->nsrcs; addr++) { + struct in_addr grp_src; + char grp_src_str[INET_ADDRSTRLEN]; + + grp_src.s_addr = htonl(record->saddr[addr]); + inet_ntop(AF_INET, &grp_src, grp_src_str, sizeof(grp_src_str)); + debug(DBG_IGMP, "received IGMPv3 record src %s", + grp_src_str); + + body_len -= sizeof(record->saddr[addr]); + pos += sizeof(record->saddr[addr]); + } + + /* Yes, EXCL, not INCL, see RFC3376 */ + if ((htonl(record->addr) == cinet_addr(224,0,2,60)) && + ((record->type == IGMP_V3_REC_MODE_IS_EXCL) || + (record->type == IGMP_V3_REC_MODE_CH_EXCL))) + igmp_match(); + + body_len -= record->auxlen; + pos += record->auxlen; + } + + break; + } + + case IGMP_MEMBERSHIP_QUERY: + debug(DBG_IGMP, "igmp_membership_query"); + break; + + case IGMP_V2_LEAVE_GROUP: + debug(DBG_IGMP, "igmp_v2_leave_group"); + break; + + default: + debug(DBG_IGMP, "IGMP msg type %02hhx", igmp_msg->common.type); + break; + } + + buf += hdr->length; + len -= hdr->length; +} + +static void +igmp_read_cb(struct uring_task *task, int res) +{ + struct igmp *igmp = container_of(task, struct igmp, task); + + assert_return(task); + assert_task_alive(DBG_IGMP, task); + + debug(DBG_IGMP, "task %p, igmp %p, res %i", task, igmp, res); + if (res < 0) { + error("res: %i", res); + return; + } + + task->tbuf->len = res; + + if (task->saddr.storage.ss_family == AF_PACKET || + task->saddr.ll.sll_protocol == htons(ETH_P_IP)) + igmp_parse(igmp); + else + debug(DBG_IGMP, "invalid packet type received"); + + uring_tbuf_read(&igmp->task, igmp_read_cb); +} + +static void +igmp_free(struct uring_task *task) +{ + struct igmp *igmp = container_of(task, struct igmp, task); + + assert_return(task); + debug(DBG_IGMP, "task %p, igmp %p", task, igmp); + xfree(igmp); +} + +void +igmp_refdump() +{ + assert_return_silent(cfg->igmp); + + uring_task_refdump(&cfg->igmp->task); +} + +void +igmp_delete() +{ + assert_return_silent(cfg->igmp); + + debug(DBG_IGMP, "closing fd %i", cfg->igmp->task.fd); + uring_task_destroy(&cfg->igmp->task); + cfg->igmp = NULL; +} + +void +igmp_init() +{ + static const struct sock_filter filter[] = { + BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct iphdr), 1, 0), /* A < sizeof(iphdr) */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 0 /* iphdr[0] */), /* A <- version + ihl */ + BPF_STMT(BPF_ALU + BPF_RSH + BPF_K, 4), /* A <- A >> 4 (version) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x04, 1, 0), /* A != 4 */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct iphdr, protocol)), /* A <- ip protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_IGMP, 1, 0), /* A != IPPROTO_IGMP */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct iphdr, daddr)), /* A <- ip dst addr */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, chtobe32(cinet_addr(224,0,2,60)), 2, 0), /* A != 224.0.2.60 */ + //BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xe000023c, 2, 0), /* A != 224.0.2.60 */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, chtobe32(cinet_addr(224,0,0,22)), 1, 0), /* A != 224.0.0.22 */ + //BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xe0000016, 1, 0), /* A != 224.0.0.22 */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0 /* iphdr[0] */), /* X <- pkt->ihl * 4 */ + BPF_STMT(BPF_LD + BPF_IMM, 20), /* A <- 20 */ + BPF_JUMP(BPF_JMP + BPF_JGT + BPF_X, 0, 0, 1), /* A > X */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct iphdr, tot_len)), /* A <- ip tot_len */ + BPF_JUMP(BPF_JMP + BPF_JGT + BPF_X, 0, 1, 0), /* A <= ip->ihl * 4 */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + BPF_STMT(BPF_ALU + BPF_SUB + BPF_X, 0), /* A <- A - X (bodylen) */ + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, 8, 1, 0), /* A < 8 */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct iphdr, tot_len)), /* A <- ip->tot_len */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), /* A != ip->tot_len */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + + BPF_STMT(BPF_RET + BPF_K, (uint32_t) -1), /* accept packet */ + }; + static const struct sock_fprog fprog = { + .len = ARRAY_SIZE(filter), + .filter = (struct sock_filter*) filter, + }; + struct sockaddr_ll addr = { + .sll_family = AF_PACKET, + .sll_ifindex = 0, + .sll_pkttype = PACKET_MULTICAST, + }; + struct igmp *igmp; + int sfd; + int opt; + + if (!cfg->do_igmp) { + debug(DBG_IGMP, "igmp snooping disabled"); + return; + } + + assert_return(!cfg->igmp); + + igmp = zmalloc(sizeof(*igmp)); + if (!igmp) + return; + + /* + * Kernel limitation, must be ETH_P_ALL, not ETH_P_IP or we won't get + * outgoing packets, https://lkml.org/lkml/1999/12/23/112 + */ + sfd = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_ALL)); + if (sfd < 0) { + if (errno == EACCES || errno == EPERM) + info("Unable to do IGMP snooping, permission denied"); + else + error("socket: %m"); + goto error; + } + + if (setsockopt(sfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0) { + error("setsockopt(SO_ATTACH_FILTER): %m"); + goto error; + } + + if (setsockopt(sfd, SOL_SOCKET, SO_LOCK_FILTER, &opt, sizeof(opt)) < 0) { + error("setsockopt(SO_LOCK_FILTER): %m"); + goto error; + } + + if (cfg->igmp_iface) { + struct ifreq ifreq; + int r; + + r = snprintf(ifreq.ifr_name, sizeof(ifreq.ifr_name), + "%s", cfg->igmp_iface); + if (r < 0 || r >= sizeof(ifreq.ifr_name)) + die("invalid interface name: %s", cfg->igmp_iface); + + if (ioctl(sfd, SIOCGIFINDEX, &ifreq) < 0) + die("ioctl: %m"); + + debug(DBG_IGMP, "using interface %s (%i)", + cfg->igmp_iface, ifreq.ifr_ifindex); + + struct packet_mreq mreq = { + .mr_ifindex = ifreq.ifr_ifindex, + .mr_type = PACKET_MR_ALLMULTI + }; + + if (setsockopt(sfd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, + &mreq, sizeof(mreq)) < 0) { + error("setsockopt(PACKET_ADD_MEMBERSHIP): %m"); + goto error; + } + } + + /* can't set .sll_protocol to htons(ETH_P_IP), see comment above */ + if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + error("bind: %m"); + goto error; + } + + debug(DBG_IGMP, "init successful, using fd %i", sfd); + uring_task_init(&igmp->task, "igmp", uring_parent(), igmp_free); + uring_task_set_fd(&igmp->task, sfd); + uring_task_set_buf(&igmp->task, &igmp->tbuf); + igmp->task.saddr.addrlen = sizeof(igmp->task.saddr.ll); + uring_tbuf_recvmsg(&igmp->task, igmp_read_cb); + + cfg->igmp = igmp; + + return; + +error: + close(sfd); + xfree(igmp); +} diff --git a/mcserverproxy/igmp.h b/mcserverproxy/igmp.h new file mode 100644 index 0000000..80875b0 --- /dev/null +++ b/mcserverproxy/igmp.h @@ -0,0 +1,10 @@ +#ifndef fooigmphfoo +#define fooigmphfoo + +void igmp_refdump(); + +void igmp_delete(); + +void igmp_init(); + +#endif diff --git a/mcserverproxy/main.c b/mcserverproxy/main.c new file mode 100644 index 0000000..047e70e --- /dev/null +++ b/mcserverproxy/main.c @@ -0,0 +1,740 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "signal-handler.h" +#include "uring.h" +#include "config-parser.h" +#include "server.h" +#include "server-config.h" +#include "announce.h" +#include "systemd.h" +#include "igmp.h" +#include "idle.h" +#include "ptimer.h" +#include + +/* Global */ +struct cfg *cfg = NULL; +bool exiting = false; +unsigned debug_mask = DBG_ERROR | DBG_INFO; + +/* Local */ +static bool daemonize = false; +static FILE *log_file = NULL; +static const char *log_file_path = NULL; + +#define ANSI_RED "\x1B[0;31m" +#define ANSI_GREEN "\x1B[0;32m" +#define ANSI_YELLOW "\x1B[0;33m" +#define ANSI_BLUE "\x1B[0;34m" +#define ANSI_MAGENTA "\x1B[0;35m" +#define ANSI_GREY "\x1B[0;38;5;245m" +#define ANSI_NORMAL "\x1B[0m" + +static void +msg(enum debug_lvl lvl, const char *fmt, va_list ap) +{ + static bool first = true; + static bool use_colors = false; + static bool sd_daemon = false; + const char *color; + const char *sd_lvl; + + assert_return(lvl != 0 && !empty_str(fmt) && ap); + + while (first) { + int fd; + const char *e; + + first = false; + + /* assume we're not launched by systemd when daemonized */ + if (daemonize) { + sd_daemon = false; + use_colors = false; + break; + } + + if (log_file) { + sd_daemon = false; + use_colors = false; + break; + } + + if (getenv("NO_COLOR")) { + sd_daemon = false; + use_colors = false; + break; + } + + fd = fileno(stderr); + if (fd < 0) { + /* Umm... */ + sd_daemon = true; + use_colors = false; + break; + } + + if (!isatty(fd)) { + sd_daemon = true; + use_colors = false; + break; + } + + /* systemd wouldn't normally set TERM */ + e = getenv("TERM"); + if (!e) { + sd_daemon = true; + use_colors = false; + break; + } + + if (streq(e, "dumb")) { + sd_daemon = false; + use_colors = false; + break; + } + + sd_daemon = false; + use_colors = true; + } + + switch (lvl) { + case DBG_ERROR: + sd_lvl = SD_ERR; + color = use_colors ? ANSI_RED : NULL; + break; + case DBG_VERBOSE: + sd_lvl = SD_INFO; + color = NULL; + break; + case DBG_INFO: + sd_lvl = SD_NOTICE; + color = NULL; + break; + default: + sd_lvl = SD_DEBUG; + color = use_colors ? ANSI_GREY : NULL; + break; + } + + if (sd_daemon) + fprintf(stderr, sd_lvl); + else if (color) + fprintf(stderr, color); + + vfprintf(log_file ? log_file : stderr, fmt, ap); + + if (color) + fprintf(stderr, ANSI_NORMAL); +} + +void +__debug(enum debug_lvl lvl, const char *fmt, ...) +{ + va_list ap; + + assert_return(lvl != 0 && !empty_str(fmt)); + + va_start(ap, fmt); + msg(lvl, fmt, ap); + va_end(ap); +} + +__attribute__((noreturn)) void +__die(const char *fmt, ...) +{ + va_list ap; + + if (!empty_str(fmt)) { + va_start(ap, fmt); + msg(DBG_ERROR, fmt, ap); + va_end(ap); + } else + error("fmt not set"); + + sd_notifyf(0, "STATUS=Error, shutting down"); + exit(EXIT_FAILURE); +}; + +static void +cfg_free(struct uring_task *task) +{ + struct cfg *xcfg = container_of(task, struct cfg, task); + + assert_return(task && xcfg == cfg); + + debug(DBG_SIG, "called"); + systemd_delete(cfg); + xfree(cfg->igmp_iface); + cfg->igmp_iface = NULL; + exiting = true; + /* The cfg struct is free:d in main() */ +} + +enum mcfg_keys { + MCFG_KEY_INVALID = 0, + MCFG_KEY_IGMP, + MCFG_KEY_IGMP_IFACE, + MCFG_KEY_ANN_INTERVAL, + MCFG_KEY_PROXY_CONN_INTERVAL, + MCFG_KEY_PROXY_CONN_ATTEMPTS, + MCFG_KEY_SOCKET_DEFER, + MCFG_KEY_SOCKET_FREEBIND, + MCFG_KEY_SOCKET_KEEPALIVE, + MCFG_KEY_SOCKET_IPTOS, + MCFG_KEY_SOCKET_NODELAY, +}; + +struct cfg_key_value_map mcfg_key_map[] = { + { + .key_name = "igmp", + .key_value = MCFG_KEY_IGMP, + .value_type = CFG_VAL_TYPE_BOOL, + }, { + .key_name = "igmp_iface", + .key_value = MCFG_KEY_IGMP_IFACE, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "announce_interval", + .key_value = MCFG_KEY_ANN_INTERVAL, + .value_type = CFG_VAL_TYPE_UINT16, + }, { + .key_name = "proxy_connection_interval", + .key_value = MCFG_KEY_PROXY_CONN_INTERVAL, + .value_type = CFG_VAL_TYPE_UINT16, + }, { + .key_name = "proxy_connection_attempts", + .key_value = MCFG_KEY_PROXY_CONN_ATTEMPTS, + .value_type = CFG_VAL_TYPE_UINT16, + }, { + .key_name = "socket_defer", + .key_value = MCFG_KEY_SOCKET_DEFER, + .value_type = CFG_VAL_TYPE_BOOL, + }, { + .key_name = "socket_freebind", + .key_value = MCFG_KEY_SOCKET_FREEBIND, + .value_type = CFG_VAL_TYPE_BOOL, + }, { + .key_name = "socket_keepalive", + .key_value = MCFG_KEY_SOCKET_KEEPALIVE, + .value_type = CFG_VAL_TYPE_BOOL, + }, { + .key_name = "socket_iptos", + .key_value = MCFG_KEY_SOCKET_IPTOS, + .value_type = CFG_VAL_TYPE_BOOL, + }, { + .key_name = "socket_nodelay", + .key_value = MCFG_KEY_SOCKET_NODELAY, + .value_type = CFG_VAL_TYPE_BOOL, + }, { + .key_name = NULL, + .key_value = MCFG_KEY_INVALID, + .value_type = CFG_VAL_TYPE_INVALID, + } +}; + +static void +cfg_read() +{ + FILE *cfgfile; + const char *path; + char buf[4096]; + char *pos = buf; + size_t rd = 0; + size_t r; + + assert_return(cfg); + + if (cfg->cfg_file) + path = cfg->cfg_file; + else + path = DEFAULT_MAIN_CFG_FILE; + + cfgfile = fopen(path, "re"); + if (!cfgfile) { + /* ENOENT is only an error with an explicitly set path */ + if (errno == ENOENT && !cfg->cfg_file) + return; + else if (errno == ENOENT) + die("main config file (%s) missing", path); + else + die("fopen(%s): %m", path); + } + + debug(DBG_CFG, "opened main config file (%s)", path); + + while (rd < sizeof(buf)) { + r = fread(pos, 1, sizeof(buf) - rd - 1, cfgfile); + if (r == 0) + break; + rd += r; + pos += r; + } + + if (rd == 0) + die("main config file (%s) zero size", path); + + if (rd >= sizeof(buf)) + die("main config file (%s) too large", path); + + fclose(cfgfile); + *pos = '\0'; + pos = buf; + + if (!config_parse_header(path, "mcproxy", &pos)) + die("main config file (%s) invalid", path); + + while (true) { + int key; + const char *keyname; + struct cfg_value value; + + if (!config_parse_line(path, &pos, mcfg_key_map, + &key, &keyname, &value)) + break; + + if (key == MCFG_KEY_INVALID) + die("main config file (%s) invalid", path); + + debug(DBG_CFG, "main cfg: key %s", keyname); + + switch (key) { + + case MCFG_KEY_IGMP: + cfg->do_igmp = value.boolean; + break; + + case MCFG_KEY_IGMP_IFACE: + cfg->igmp_iface = xstrdup(value.str); + if (!cfg->igmp_iface) + die("xstrdup: %m"); + + break; + + case MCFG_KEY_ANN_INTERVAL: + cfg->announce_interval = value.uint16; + break; + + case MCFG_KEY_PROXY_CONN_INTERVAL: + cfg->proxy_connection_interval = value.uint16; + break; + + case MCFG_KEY_PROXY_CONN_ATTEMPTS: + cfg->proxy_connection_attempts = value.uint16; + break; + + case MCFG_KEY_SOCKET_DEFER: + cfg->socket_defer = value.boolean; + break; + + case MCFG_KEY_SOCKET_FREEBIND: + cfg->socket_freebind = value.boolean; + break; + + case MCFG_KEY_SOCKET_KEEPALIVE: + cfg->socket_keepalive = value.boolean; + break; + + case MCFG_KEY_SOCKET_IPTOS: + cfg->socket_iptos = value.boolean; + break; + + case MCFG_KEY_SOCKET_NODELAY: + cfg->socket_nodelay = value.boolean; + break; + + case MCFG_KEY_INVALID: + default: + die("main config file (%s) invalid", path); + } + } +} + +const struct { + const char *name; + unsigned val; +} debug_category_str[] = { + { + .name = "config", + .val = DBG_CFG, + },{ + .name = "refcount", + .val = DBG_REF, + },{ + .name = "malloc", + .val = DBG_MALLOC, + },{ + .name = "announce", + .val = DBG_ANN, + },{ + .name = "signal", + .val = DBG_SIG, + },{ + .name = "uring", + .val = DBG_UR, + },{ + .name = "server", + .val = DBG_SRV, + },{ + .name = "proxy", + .val = DBG_PROXY, + },{ + .name = "rcon", + .val = DBG_RCON, + },{ + .name = "idle", + .val = DBG_IDLE, + },{ + .name = "igmp", + .val = DBG_IGMP, + },{ + .name = "systemd", + .val = DBG_SYSD, + },{ + .name = "dns", + .val = DBG_DNS, + },{ + .name = "timer", + .val = DBG_TIMER, + },{ + .name = NULL, + .val = 0, + } +}; + +__attribute__((noreturn)) static void +usage(int argc, char **argv, bool invalid) +{ + if (invalid) + info("Invalid option(s)"); + + info("Usage: %s [OPTIONS]\n" + "\n" + "Valid options:\n" + " -c, --cfgdir=DIR\tlook for configuration files in DIR\n" + " -u, --user=USER\trun as USER\n" + " -D, --daemonize\trun in daemon mode (disables stderr output)\n" + " -l, --logfile=FILE\tlog to FILE instead of stderr\n" + " -h, --help\t\tprint this information\n" + " -v, --verbose\t\tenable verbose logging\n" + " -d, --debug=CATEGORY\tenable debugging for CATEGORY\n" + "\t\t\t(use \"list\" to see available categories,\n" + "\t\t\t or \"all\" to enable all categories)\n", + argv ? argv[0] : "mcproxy"); + + exit(invalid ? EXIT_FAILURE : EXIT_SUCCESS); +} + +static void +cfg_init(int argc, char **argv) +{ + int c; + unsigned i; + + assert_die(argc > 0 && argv, "invalid arguments"); + + cfg = zmalloc(sizeof(*cfg)); + if (!cfg) + die("malloc: %m"); + + uring_task_init(&cfg->task, "main", NULL, cfg_free); + list_init(&cfg->servers); + + cfg->cfg_dir = DEFAULT_CFG_DIR; + cfg->announce_interval = DEFAULT_ANNOUNCE_INTERVAL; + cfg->proxy_connection_interval = DEFAULT_PROXY_CONN_INTERVAL; + cfg->proxy_connection_attempts = DEFAULT_PROXY_CONN_ATTEMPTS; + cfg->socket_defer = DEFAULT_SOCKET_DEFER; + cfg->socket_freebind = DEFAULT_SOCKET_FREEBIND; + cfg->socket_keepalive = DEFAULT_SOCKET_KEEPALIVE; + cfg->socket_iptos = DEFAULT_SOCKET_IPTOS; + cfg->socket_nodelay = DEFAULT_SOCKET_NODELAY; + cfg->uid = geteuid(); + cfg->gid = getegid(); + + while (true) { + int option_index = 0; + static struct option long_options[] = { + { "cfgdir", required_argument, 0, 'c' }, + { "cfgfile", required_argument, 0, 'C' }, + { "user", required_argument, 0, 'u' }, + { "daemonize", no_argument, 0, 'D' }, + { "logfile", required_argument, 0, 'l' }, + { "help", no_argument, 0, 'h' }, + { "verbose", no_argument, 0, 'v' }, + { "debug", required_argument, 0, 'd' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, ":c:C:u:Dl:hvd:", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'c': + cfg->cfg_dir = optarg; + break; + + case 'C': + cfg->cfg_file = optarg; + break; + + case 'v': + debug_mask |= DBG_VERBOSE; + break; + + case 'D': + daemonize = true; + break; + + case 'l': + log_file_path = optarg; + break; + + case 'u': { + struct passwd *pwd; + + errno = 0; + pwd = getpwnam(optarg); + if (!pwd) { + if (errno == 0) + errno = ESRCH; + if (errno == ESRCH) + die("failed to find user %s", optarg); + else + die("failed to find user %s (%m)", optarg); + } + + debug(DBG_CFG, "asked to execute with uid %ji gid %ji", + (intmax_t)pwd->pw_uid, + (intmax_t)pwd->pw_gid); + cfg->uid = pwd->pw_uid; + cfg->gid = pwd->pw_gid; + break; + } + + case 'd': + if (strcaseeq(optarg, "all")) { + debug_mask = ~0; + break; + } else if (strcaseeq(optarg, "list")) { + info("Debug categories:"); + info(" * all"); + for (i = 0; debug_category_str[i].name; i++) + info(" * %s", debug_category_str[i].name); + exit(EXIT_FAILURE); + } + + for (i = 0; debug_category_str[i].name; i++) { + if (strcaseeq(optarg, debug_category_str[i].name)) + break; + } + + if (!debug_category_str[i].name) + usage(argc, argv, true); + + debug_mask |= DBG_VERBOSE; + debug_mask |= debug_category_str[i].val; + break; + + case 'h': + usage(argc, argv, false); + + default: + usage(argc, argv, true); + } + + } + + if (optind < argc) + usage(argc, argv, true); +} + +static void +cfg_apply() +{ + if (cfg->uid == 0 || cfg->gid == 0) + /* This catches both -u root and running as root without -u */ + die("Execution as root is not supported (use -u )"); + + capng_clear(CAPNG_SELECT_BOTH); + if (capng_updatev(CAPNG_ADD, + CAPNG_EFFECTIVE | CAPNG_PERMITTED, + CAP_NET_RAW, CAP_NET_BIND_SERVICE, -1)) + die("capng_updatev failed"); + + if (geteuid() != cfg->uid) { + if (capng_change_id(cfg->uid, + cfg->gid, + CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING)) + die("capng_change_id failed"); + } else { + /* + * This can fail if any of the caps are lacking, but it'll + * be re-checked later. + */ + capng_apply(CAPNG_SELECT_BOTH); + setgroups(0, NULL); + } + + if (daemonize) { + if (daemon(1, 0) < 0) + die("daemon() failed: %m"); + } + + if (log_file_path) { + log_file = fopen(log_file_path, "ae"); + if (!log_file) + die("fopen(%s) failed: %m", log_file_path); + } + + /* + * Do this after caps have been dropped to make sure we're not + * accessing a directory we should have permissions to. + */ + if (chdir(cfg->cfg_dir)) + die("chdir(%s): %m", cfg->cfg_dir); + + if (debug_enabled(DBG_VERBOSE)) { + char *wd; + + wd = get_current_dir_name(); + verbose("Working directory: %s", wd ? wd : ""); + free(wd); + } +} + +void +dump_tree() +{ + struct server *server; + + if (!debug_enabled(DBG_REF)) + return; + + info("\n\n"); + info("Dumping Tree"); + info("============"); + uring_task_refdump(&cfg->task); + uring_refdump(); + signal_refdump(); + ptimer_refdump(); + idle_refdump(); + igmp_refdump(); + announce_refdump(); + server_cfg_monitor_refdump(); + list_for_each_entry(server, &cfg->servers, list) + server_refdump(server); + info("============"); + info("\n\n"); +} + +int +main(int argc, char **argv) +{ + struct server *server; + unsigned server_count; + struct rlimit old_rlimit; + + cfg_init(argc, argv); + + cfg_apply(); + + cfg_read(); + + /* + * In the splice case we use 4 fds per proxy connection... + */ + if (prlimit(0, RLIMIT_NOFILE, NULL, &old_rlimit) == 0) { + struct rlimit new_rlimit; + + new_rlimit.rlim_cur = old_rlimit.rlim_max; + new_rlimit.rlim_max = old_rlimit.rlim_max; + + if (prlimit(0, RLIMIT_NOFILE, &new_rlimit, NULL) == 0) + debug(DBG_MALLOC, "prlimit(NOFILE): %u/%u -> %u/%u", + (unsigned)old_rlimit.rlim_cur, + (unsigned)old_rlimit.rlim_max, + (unsigned)new_rlimit.rlim_cur, + (unsigned)new_rlimit.rlim_cur); + } + + uring_init(); + + ptimer_init(); + + igmp_init(); + + /* Drop CAP_NET_RAW (if we have it), only used for igmp */ + capng_clear(CAPNG_SELECT_BOTH); + if (capng_update(CAPNG_ADD, + CAPNG_EFFECTIVE | CAPNG_PERMITTED, + CAP_NET_BIND_SERVICE)) + die("capng_update failed"); + + if (capng_apply(CAPNG_SELECT_BOTH)) { + /* Try clearing all caps, shouldn't fail */ + capng_clear(CAPNG_SELECT_BOTH); + if (capng_apply(CAPNG_SELECT_BOTH)) + die("capng_apply failed"); + } + + signal_init(); + + server_cfg_monitor_init(); + + announce_init(); + + if (!cfg->igmp) + announce_start(0); + + idle_init(); + + uring_task_put(&cfg->task); + + server_count = 0; + list_for_each_entry(server, &cfg->servers, list) + server_count++; + + sd_notifyf(0, "READY=1\n" + "STATUS=Running, %u server configurations loaded\n" + "MAINPID=%lu", + server_count, + (unsigned long)getpid()); + + info("mcproxy (%s) started, %u server configurations loaded", + VERSION, server_count); + + uring_event_loop(); + + verbose("Exiting"); + + xfree(cfg); + cfg = NULL; + + if (debug_enabled(DBG_MALLOC)) + debug_resource_usage(); + + fflush(stdout); + fflush(stderr); + exit(EXIT_SUCCESS); +} diff --git a/mcserverproxy/main.h b/mcserverproxy/main.h new file mode 100644 index 0000000..256ddae --- /dev/null +++ b/mcserverproxy/main.h @@ -0,0 +1,175 @@ +#ifndef foomainhfoo +#define foomainhfoo + +#include +#include + +struct cfg; + +#include "utils.h" + +extern struct cfg *cfg; +extern bool exiting; +extern unsigned debug_mask; + +enum debug_lvl { + DBG_ERROR = (0x1 << 1), + DBG_INFO = (0x1 << 2), + DBG_VERBOSE = (0x1 << 3), + DBG_CFG = (0x1 << 4), + DBG_REF = (0x1 << 5), + DBG_MALLOC = (0x1 << 6), + DBG_ANN = (0x1 << 7), + DBG_SIG = (0x1 << 8), + DBG_UR = (0x1 << 9), + DBG_SRV = (0x1 << 10), + DBG_PROXY = (0x1 << 11), + DBG_RCON = (0x1 << 12), + DBG_IDLE = (0x1 << 13), + DBG_IGMP = (0x1 << 14), + DBG_SYSD = (0x1 << 15), + DBG_DNS = (0x1 << 16), + DBG_TIMER = (0x1 << 17), +}; + +static inline bool +debug_enabled(enum debug_lvl lvl) +{ + return !!(lvl & debug_mask); +} + +void __debug(enum debug_lvl lvl, const char *fmt, ...) __attribute__((format(printf, 2, 3))); + +#define __ifdebug(lvl, fmt, ...) \ + do { \ + if (debug_enabled((lvl))) \ + __debug((lvl), fmt "\n"__VA_OPT__(,) __VA_ARGS__); \ + } while (0) + +#define debug(lvl, fmt, ...) __ifdebug((lvl), "%s:%s:%i: " fmt, \ + __FILE__, __func__, __LINE__ \ + __VA_OPT__(,) __VA_ARGS__) +#define verbose(fmt, ...) __ifdebug(DBG_VERBOSE, fmt, __VA_ARGS__) +#define info(fmt, ...) __ifdebug(DBG_INFO, fmt, __VA_ARGS__) +#define error(fmt, ...) __ifdebug(DBG_ERROR, "%s:%s:%i: " fmt, \ + __FILE__, __func__, __LINE__ \ + __VA_OPT__(,) __VA_ARGS__) + +void __die(const char *fmt, ...) __attribute__((format(printf, 1, 2))); + +#define die(fmt, ...) \ + __die("%s:%s:%i: " fmt "\n", \ + __FILE__, __func__, __LINE__ \ + __VA_OPT__(,) __VA_ARGS__) + +#define assert_log(expr, msg) \ + ((expr) ? \ + (true) : \ + (__debug(DBG_ERROR, "%s:%s:%i: assertion \"" msg "\" failed\n", \ + __FILE__, __func__, __LINE__), false)) + +#define assert_return(expr, ...) \ + do { \ + if (!assert_log(expr, #expr)) \ + return __VA_ARGS__; \ + } while (0) + +#define assert_return_silent(expr, ...) \ + do { \ + if (!(expr)) \ + return __VA_ARGS__; \ + } while (0) + +#define assert_die(expr, msg) \ + do { \ + if (!assert_log(expr, #expr)) \ + die(msg); \ + } while (0) + +#define assert_task_alive_or(lvl, t, cmd) \ +do { \ + if (!(t)) { \ + error("invalid task"); \ + cmd; \ + } \ + \ + if ((t)->dead) { \ + debug((lvl), "task dead"); \ + cmd; \ + } \ +} while(0) + +#define assert_task_alive(lvl, t) assert_task_alive_or((lvl), (t), return) + +void dump_tree(); + +struct uring_task; + +/* To save typing in all the function definitions below */ +typedef void (*utask_cb_t)(struct uring_task *, int res); +typedef int (*rutask_cb_t)(struct uring_task *, int res); + +struct uring_task_buf { + char buf[4096]; + size_t len; + size_t done; + struct iovec iov; + struct msghdr msg; +}; + +struct uring_task { + const char *name; + unsigned refcount; + int fd; + struct uring_task *parent; + void (*free)(struct uring_task *); + bool dead; + struct uring_task_buf *tbuf; + + /* called once or repeatedly until is_complete_cb is satisfied */ + utask_cb_t cb; + + /* returns: 0 = not complete; < 0 = error; > 0 = complete */ + rutask_cb_t is_complete_cb; + + /* called once tbuf processing is done */ + utask_cb_t final_cb; + + /* used for recvmsg/sendmsg */ + struct saddr saddr; + void *priv; +}; + +struct cfg { + /* Options */ + uid_t uid; + gid_t gid; + const char *cfg_dir; + const char *cfg_file; + bool do_igmp; + char *igmp_iface; + bool splice_supported; + uint16_t announce_interval; + uint16_t proxy_connection_interval; + uint16_t proxy_connection_attempts; + bool socket_defer; + bool socket_freebind; + bool socket_keepalive; + bool socket_iptos; + bool socket_nodelay; + + /* Bookkeeping */ + struct uring_ev *uring; + struct server_cfg_monitor *server_cfg_monitor; + struct signal_ev *signal; + struct announce *announce; + struct ptimer *ptimer; + struct igmp *igmp; + struct idle *idle; + struct sd_bus *sd_bus; + bool sd_bus_failed; + struct uring_task task; + struct list_head servers; +}; + +#endif diff --git a/mcserverproxy/meson.build b/mcserverproxy/meson.build new file mode 100644 index 0000000..e5fa7bc --- /dev/null +++ b/mcserverproxy/meson.build @@ -0,0 +1,36 @@ +mcproxy_sources = [ + 'main.c', + 'uring.c', + 'signal-handler.c', + 'server.c', + 'server-proxy.c', + 'server-config.c', + 'server-rcon.c', + 'announce.c', + 'config-parser.c', + 'idle.c', + 'ptimer.c', + 'igmp.c', + 'systemd.c', + 'utils.c' +] + +dep_liburing = dependency('liburing') +dep_libsystemd = dependency('libsystemd') +dep_libcapng = dependency('libcap-ng') + +mcproxy_deps = [ + dep_liburing, + dep_libsystemd, + dep_libcapng, + dep_config_h, + dep_libshared, +] + +executable( + 'mcproxy', + mcproxy_sources, + link_args: [ '-lanl' ], + dependencies: mcproxy_deps, +) + diff --git a/mcserverproxy/ptimer.c b/mcserverproxy/ptimer.c new file mode 100644 index 0000000..5f9cf5d --- /dev/null +++ b/mcserverproxy/ptimer.c @@ -0,0 +1,223 @@ +#include +#include +#include +#include +#include + +#include "main.h" +#include "uring.h" +#include "ptimer.h" + +struct ptimer { + uint64_t value; + time_t previous_time; + struct uring_task task; + unsigned task_count; + struct list_head ptasks; +}; + +static void +ptimer_set(unsigned value, unsigned interval) +{ + struct itimerspec tspec = { + .it_interval = { + .tv_sec = interval, + .tv_nsec = 0 + }, + .it_value = { + .tv_sec = value, + .tv_nsec = 0 + } + }; + + assert_return(cfg->ptimer && cfg->ptimer->task.fd >= 0); + + if (timerfd_settime(cfg->ptimer->task.fd, 0, &tspec, NULL) != 0) + error("timerfd_settime: %m"); +} + +static unsigned +gcd(unsigned a, unsigned b) +{ + if (a == 0) + return b; + return gcd(b % a, a); +} + +static unsigned +array_gcd(unsigned arr[], unsigned n) +{ + unsigned result = arr[0]; + + for (unsigned i = 1; i < n; i++) + result = gcd(arr[i], result); + + return result; +} + +static void +ptimer_tick(struct ptimer *ptimer) +{ + time_t now = time(NULL); + unsigned diff = (unsigned)(now - ptimer->previous_time); + struct ptimer_task *ptask, *ptmp; + + debug(DBG_TIMER, "got a tick of %u secs", diff); + list_for_each_entry_safe(ptask, ptmp, &ptimer->ptasks, list) { + if (ptask->remain > diff) { + ptask->remain -= diff; + continue; + } + + debug(DBG_TIMER, "triggering ptask %p (times %u)", + ptask, ptask->times); + + ptask->cb(ptask); + ptask->remain = ptask->interval; + + if (ptask->times == 0) + continue; + + ptask->times--; + if (ptask->times == 0) { + ptask->active = false; + list_del(&ptask->list); + } + } + + ptimer->previous_time = now; +} + +static void +ptimer_reconfig(struct ptimer *ptimer) +{ + struct ptimer_task *ptask; + unsigned i = 0; + unsigned lowest = ~0; + unsigned interval; + + if (list_empty(&ptimer->ptasks)) { + debug(DBG_TIMER, "no tasks"); + ptimer_set(0, 0); + return; + } + + unsigned intervals[ptimer->task_count]; + + list_for_each_entry(ptask, &ptimer->ptasks, list) { + if (ptask->remain < lowest) + lowest = ptask->remain; + intervals[i++] = ptask->interval; + } + + interval = array_gcd(intervals, i); + + debug(DBG_TIMER, "lowest: %u, gcd: %u\n", lowest, interval); + ptimer_set(lowest, interval); +} + +void +ptimer_del_task(struct ptimer_task *ptask) +{ + struct ptimer *ptimer = cfg->ptimer; + + assert_return(ptask && ptimer); + assert_return_silent(ptask->active); + assert_return(ptimer->task_count > 0); + + list_del(&ptask->list); + ptask->active = false; + ptimer->task_count--; + ptimer_tick(ptimer); + ptimer_reconfig(ptimer); + uring_task_put(&ptimer->task); +} + +void +ptimer_add_task(struct ptimer_task *ptask) +{ + struct ptimer *ptimer = cfg->ptimer; + + assert_return(ptask && ptask->interval > 0 && ptask->cb && ptimer); + assert_return_silent(!ptask->active); + + uring_task_get(&ptimer->task); + ptask->active = true; + ptask->remain = ptask->interval; + ptimer_tick(ptimer); + list_add(&ptask->list, &ptimer->ptasks); + ptimer->task_count++; + ptimer_reconfig(ptimer); +} + +void +ptimer_refdump() +{ + assert_return(cfg->ptimer); + + uring_task_refdump(&cfg->ptimer->task); +} + +static void +ptimer_free(struct uring_task *task) +{ + struct ptimer *ptimer = container_of(task, struct ptimer, task); + + assert_return(task); + + debug(DBG_TIMER, "task %p, ptimer %p", task, ptimer); + xfree(ptimer); + cfg->ptimer = NULL; +} + +void +ptimer_delete() +{ + assert_return(cfg->ptimer); + + debug(DBG_TIMER, "closing fd %i", cfg->ptimer->task.fd); + uring_task_destroy(&cfg->ptimer->task); +} + +static void +ptimer_cb(struct uring_task *task, int res) +{ + struct ptimer *ptimer = container_of(task, struct ptimer, task); + + assert_return(task); + assert_task_alive(DBG_IGMP, task); + + if (res != sizeof(ptimer->value)) { + error("timerfd_read: res: %i, %m", res); + return; + } + + ptimer_tick(ptimer); + uring_read(&ptimer->task, &ptimer->value, sizeof(ptimer->value), ptimer_cb); +} + +void +ptimer_init() +{ + struct ptimer *ptimer; + int tfd; + + assert_return(!cfg->ptimer); + + ptimer = zmalloc(sizeof(*ptimer)); + if (!ptimer) + die("malloc: %m"); + + tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); + if (tfd < 0) + die("timerfd_create: %m"); + + ptimer->task_count = 0; + ptimer->previous_time = time(NULL); + list_init(&ptimer->ptasks); + uring_task_init(&ptimer->task, "ptimer", uring_parent(), ptimer_free); + uring_task_set_fd(&ptimer->task, tfd); + cfg->ptimer = ptimer; + uring_read(&ptimer->task, &ptimer->value, sizeof(ptimer->value), ptimer_cb); +} + diff --git a/mcserverproxy/ptimer.h b/mcserverproxy/ptimer.h new file mode 100644 index 0000000..0b53590 --- /dev/null +++ b/mcserverproxy/ptimer.h @@ -0,0 +1,33 @@ +#ifndef fooptimerhfoo +#define fooptimerhfoo + +struct ptimer_task { + unsigned interval; + unsigned times; + void (*cb)(struct ptimer_task *); + bool active; + unsigned remain; + struct list_head list; +}; + +static inline void +ptask_init(struct ptimer_task *ptask, unsigned interval, + unsigned times, void(*cb)(struct ptimer_task *)) +{ + ptask->interval = interval; + ptask->times = times; + ptask->cb = cb; + ptask->active = false; +} + +void ptimer_del_task(struct ptimer_task *ptask); + +void ptimer_add_task(struct ptimer_task *ptask); + +void ptimer_refdump(); + +void ptimer_delete(); + +void ptimer_init(); + +#endif diff --git a/mcserverproxy/server-config.c b/mcserverproxy/server-config.c new file mode 100644 index 0000000..549cf16 --- /dev/null +++ b/mcserverproxy/server-config.c @@ -0,0 +1,580 @@ +#define _GNU_SOURCE +#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" + +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); +} + +enum scfg_keys { + SCFG_KEY_INVALID = 0, + SCFG_KEY_TYPE, + SCFG_KEY_NAME, + SCFG_KEY_PORT, + SCFG_KEY_LOCAL, + SCFG_KEY_REMOTE, + SCFG_KEY_IDLE_TIMEOUT, + SCFG_KEY_STOP_METHOD, + SCFG_KEY_START_METHOD, + SCFG_KEY_STOP_EXEC, + SCFG_KEY_START_EXEC, + SCFG_KEY_RCON, + SCFG_KEY_RCON_PASSWORD, + SCFG_KEY_SYSTEMD_SERVICE, +}; + +struct cfg_key_value_map scfg_key_map[] = { + { + .key_name = "type", + .key_value = SCFG_KEY_TYPE, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "name", + .key_value = SCFG_KEY_NAME, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "port", + .key_value = SCFG_KEY_PORT, + .value_type = CFG_VAL_TYPE_UINT16, + }, { + .key_name = "local", + .key_value = SCFG_KEY_LOCAL, + .value_type = CFG_VAL_TYPE_ASYNC_ADDRS, + }, { + .key_name = "remote", + .key_value = SCFG_KEY_REMOTE, + .value_type = CFG_VAL_TYPE_ASYNC_ADDRS, + }, { + .key_name = "idle_timeout", + .key_value = SCFG_KEY_IDLE_TIMEOUT, + .value_type = CFG_VAL_TYPE_UINT16, + }, { + .key_name = "stop_method", + .key_value = SCFG_KEY_STOP_METHOD, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "start_method", + .key_value = SCFG_KEY_START_METHOD, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "stop_exec", + .key_value = SCFG_KEY_STOP_EXEC, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "start_exec", + .key_value = SCFG_KEY_START_EXEC, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "rcon", + .key_value = SCFG_KEY_RCON, + .value_type = CFG_VAL_TYPE_ASYNC_ADDRS, + }, { + .key_name = "rcon_password", + .key_value = SCFG_KEY_RCON_PASSWORD, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "systemd_service", + .key_value = SCFG_KEY_SYSTEMD_SERVICE, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = NULL, + .key_value = SCFG_KEY_INVALID, + .value_type = CFG_VAL_TYPE_INVALID, + } +}; + +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->name, "server", &pos)) + 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)) + 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); +} + +static bool +scfg_valid_filename(const char *name) +{ + const char *suffix; + + if (empty_str(name)) + return false; + if (name[0] == '.') + return false; + if ((suffix = strrchr(name, '.')) == NULL) + return false; + if (!streq(suffix, ".server")) + return false; + + return true; +} + +struct server_cfg_monitor { + struct uring_task task; + char buf[4096] __attribute__((aligned(__alignof__(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 (!scfg_valid_filename(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 (dent->d_type != DT_REG && dent->d_type != DT_UNKNOWN) + continue; + if (!scfg_valid_filename(dent->d_name)) + continue; + + server = server_new(dent->d_name); + if (server) + uring_openat(&server->task, server->name, scfg_open_cb); + } + + closedir(dir); +} + diff --git a/mcserverproxy/server-config.h b/mcserverproxy/server-config.h new file mode 100644 index 0000000..590dae0 --- /dev/null +++ b/mcserverproxy/server-config.h @@ -0,0 +1,10 @@ +#ifndef fooserverconfighfoo +#define fooserverconfighfoo + +void server_cfg_monitor_delete(); + +void server_cfg_monitor_refdump(); + +void server_cfg_monitor_init(); + +#endif diff --git a/mcserverproxy/server-proxy.c b/mcserverproxy/server-proxy.c new file mode 100644 index 0000000..4cbbb87 --- /dev/null +++ b/mcserverproxy/server-proxy.c @@ -0,0 +1,578 @@ +#define _GNU_SOURCE +#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 "utils.h" + +static void +format_bytes(char *buf, size_t len, uint64_t val) +{ + uint64_t tmp; + const char *suffix = "B"; + + assert_return(buf && len > 0); + + tmp = val * 10; + if (val > 1152921504606846976ULL) { + tmp = val / 115292150460684697ULL; + suffix= "EiB"; + } else if (val > 1125899906842624ULL) { + tmp /= 1125899906842624ULL; + suffix = "PiB"; + } else if (val > 1099511627776ULL) { + tmp /= 1099511627776ULL; + suffix = "TiB"; + } else if (val > 1073741824ULL) { + tmp /= 1073741824ULL; + suffix = "GiB"; + } else if (val > 1048576) { + tmp /= 1048576; + suffix = "MiB"; + } else if (val > 1024) { + tmp /= 1024; + suffix = "KiB"; + } + + snprintf(buf, len, "%lu.%lu %s", tmp / 10, tmp % 10, suffix); +} + +static void +format_time(char *buf, size_t len, time_t diff) +{ + unsigned hh, mm, ss; + + assert_return(buf && len > 0); + + hh = diff / 3600; + diff %= 3600; + mm = diff / 60; + diff %= 60; + ss = diff; + + snprintf(buf, len, "%02u:%02u:%02u", hh, mm, ss); +} + +static void +proxy_free(struct uring_task *task) +{ + struct server_proxy *proxy = container_of(task, struct server_proxy, task); + char cts[100]; + char stc[100]; + char duration[100]; + + assert_return(task); + + debug(DBG_PROXY, "server: %s, src: %s, dst: %s", + proxy->server->name, + proxy->client_conn.remote.addrstr, + proxy->server_conn.remote.addrstr); + + if (proxy->begin > 0) { + format_time(duration, sizeof(duration), time(NULL) - proxy->begin); + format_bytes(cts, sizeof(cts), proxy->client_bytes); + format_bytes(stc, sizeof(stc), proxy->server_bytes); + + info("%s: proxy connection %s -> %s closed " + "(CtS: %s, StC: %s), duration %s", + proxy->server->name, + proxy->client_conn.remote.addrstr, + proxy->server_conn.remote.addrstr, + cts, stc, duration); + } + + list_del(&proxy->list); + xfree(proxy); +} + +static void +proxy_client_free(struct uring_task *task) +{ + struct server_proxy *proxy = container_of(task, struct server_proxy, clienttask); + + assert_return(task); + + debug(DBG_PROXY, "%s: client connection closed", proxy->server->name); +} + +static void +proxy_server_free(struct uring_task *task) +{ + struct server_proxy *proxy = container_of(task, struct server_proxy, servertask); + + assert_return(task); + + debug(DBG_PROXY, "%s: server connection closed", proxy->server->name); +} + +void +proxy_delete(struct server_proxy *proxy) +{ + debug(DBG_PROXY, "%s: shutting down proxy %p", proxy->server->name, proxy); + + assert_return(proxy); + + ptimer_del_task(&proxy->ptask); + + if (cfg->splice_supported) { + uring_close(&proxy->server->task, proxy->cpipe[PIPE_RD]); + uring_close(&proxy->server->task, proxy->cpipe[PIPE_WR]); + uring_close(&proxy->server->task, proxy->spipe[PIPE_RD]); + uring_close(&proxy->server->task, proxy->spipe[PIPE_WR]); + } + + uring_task_set_fd(&proxy->servertask, proxy->sfd); + uring_task_destroy(&proxy->servertask); + uring_task_set_fd(&proxy->clienttask, proxy->cfd); + uring_task_destroy(&proxy->clienttask); + uring_task_destroy(&proxy->task); +} + +/* + * These four functions provide the fallback read-write mode + */ +static void proxy_client_read(struct uring_task *task, int res); + +static void +proxy_client_written(struct uring_task *task, int res) +{ + struct server_proxy *proxy = container_of(task, struct server_proxy, clienttask); + + assert_return(task); + assert_task_alive(DBG_PROXY, task); + + if (res <= 0) { + debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); + proxy_delete(proxy); + return; + } + + proxy->client_bytes += res; + uring_task_set_fd(&proxy->clienttask, proxy->cfd); + uring_tbuf_read(task, proxy_client_read); +} + +static void +proxy_client_read(struct uring_task *task, int res) +{ + struct server_proxy *proxy = container_of(task, struct server_proxy, clienttask); + + assert_return(task); + assert_task_alive(DBG_PROXY, task); + + if (res <= 0) { + debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); + proxy_delete(proxy); + return; + } + + uring_task_set_fd(&proxy->clienttask, proxy->sfd); + uring_tbuf_write(task, proxy_client_written); +} + +static void proxy_server_read(struct uring_task *task, int res); + +static void +proxy_server_written(struct uring_task *task, int res) +{ + struct server_proxy *proxy = container_of(task, struct server_proxy, servertask); + + assert_return(task); + assert_task_alive(DBG_PROXY, task); + + if (res <= 0) { + debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); + proxy_delete(proxy); + return; + } + + proxy->server_bytes += res; + uring_task_set_fd(&proxy->servertask, proxy->sfd); + uring_tbuf_read(&proxy->servertask, proxy_server_read); +} + +static void +proxy_server_read(struct uring_task *task, int res) +{ + struct server_proxy *proxy = container_of(task, struct server_proxy, servertask); + + assert_return(task); + assert_task_alive(DBG_PROXY, task); + + if (res <= 0) { + debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); + proxy_delete(proxy); + return; + } + + uring_task_set_fd(&proxy->servertask, proxy->cfd); + uring_tbuf_write(task, proxy_server_written); +} + +/* + * These four functions provide the splice fd->pipe->fd mode + */ +static void proxy_client_spliced_in(struct uring_task *task, int res); + +static void +proxy_client_spliced_out(struct uring_task *task, int res) +{ + struct server_proxy *proxy = container_of(task, struct server_proxy, servertask); + + assert_return(task); + assert_task_alive(DBG_PROXY, task); + + if (res <= 0) { + debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); + proxy_delete(proxy); + return; + } + + uring_splice(task, proxy->cfd, proxy->cpipe[PIPE_WR], proxy_client_spliced_in); +} + +static void +proxy_client_spliced_in(struct uring_task *task, int res) +{ + struct server_proxy *proxy = container_of(task, struct server_proxy, servertask); + + assert_return(task); + assert_task_alive(DBG_PROXY, task); + + if (res <= 0) { + debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); + proxy_delete(proxy); + return; + } + + uring_splice(task, proxy->cpipe[PIPE_RD], proxy->sfd, proxy_client_spliced_out); +} + +static void proxy_server_spliced_in(struct uring_task *task, int res); + +static void +proxy_server_spliced_out(struct uring_task *task, int res) +{ + struct server_proxy *proxy = container_of(task, struct server_proxy, servertask); + + assert_return(task); + assert_task_alive(DBG_PROXY, task); + + if (res <= 0) { + debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); + proxy_delete(proxy); + return; + } + + uring_splice(task, proxy->sfd, proxy->spipe[PIPE_WR], proxy_server_spliced_in); +} + +static void +proxy_server_spliced_in(struct uring_task *task, int res) +{ + struct server_proxy *proxy = container_of(task, struct server_proxy, servertask); + + assert_return(task); + assert_task_alive(DBG_PROXY, task); + + if (res <= 0) { + debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); + proxy_delete(proxy); + return; + } + + uring_splice(task, proxy->spipe[PIPE_RD], proxy->cfd, proxy_server_spliced_out); +} + +static void +proxy_connected_cb(struct connection *conn, bool connected) +{ + struct server_proxy *proxy = container_of(conn, struct server_proxy, server_conn); + + assert_return(conn); + assert_task_alive(DBG_PROXY, &proxy->clienttask); + assert_task_alive(DBG_PROXY, &proxy->servertask); + + proxy->connecting = false; + if (!connected) { + error("%s: proxy connection to remote server failed", + proxy->server->name); + if (!proxy->ptask.active) + proxy_delete(proxy); + return; + } + + ptimer_del_task(&proxy->ptask); + + proxy->sfd = proxy->servertask.fd; + verbose("%s: proxy connection %s -> %s opened", + proxy->server->name, + proxy->client_conn.remote.addrstr, + proxy->server_conn.remote.addrstr); + proxy->begin = time(NULL); + + if (cfg->splice_supported) { + debug(DBG_PROXY, "handling proxy connection with splice"); + uring_splice(&proxy->clienttask, proxy->cfd, proxy->cpipe[PIPE_WR], proxy_client_spliced_in); + uring_splice(&proxy->servertask, proxy->sfd, proxy->spipe[PIPE_WR], proxy_server_spliced_in); + } else { + debug(DBG_PROXY, "handling proxy connection with read-write"); + uring_tbuf_read(&proxy->clienttask, proxy_client_read); + uring_tbuf_read(&proxy->servertask, proxy_server_read); + } +} + +void +proxy_refdump(struct server_proxy *proxy) +{ + assert_return(proxy); + + uring_task_refdump(&proxy->task); + uring_task_refdump(&proxy->clienttask); + uring_task_refdump(&proxy->servertask); +} + +static void +proxy_connect_timer_cb(struct ptimer_task *ptask) +{ + struct server_proxy *proxy = container_of(ptask, struct server_proxy, ptask); + + assert_return(ptask); + + if (proxy->connecting) + return; + + proxy->connecting = true; + connect_any(&proxy->servertask, &proxy->server->remotes, + &proxy->server_conn, proxy_connected_cb); +} + +struct server_proxy * +proxy_new(struct server *server, struct saddr *client, int fd) +{ + struct server_proxy *proxy; + + assert_return(server && client && fd > 0, NULL); + + proxy = zmalloc(sizeof(*proxy)); + if (!proxy) { + error("malloc: %m"); + goto out; + } + + if (cfg->splice_supported) { + if (pipe2(proxy->cpipe, O_CLOEXEC) < 0) { + error("pipe2: %m"); + goto out_free; + } + + if (pipe2(proxy->spipe, O_CLOEXEC) < 0) { + error("pipe2: %m"); + goto out_close_cpipe; + } + } + + proxy->sfd = -1; + proxy->cfd = fd; + proxy->server = server; + uring_task_init(&proxy->task, "proxy", &server->task, proxy_free); + + connection_set_local(&proxy->client_conn, fd); + connection_set_remote(&proxy->client_conn, client); + + uring_task_init(&proxy->clienttask, "proxy_client", &proxy->task, + proxy_client_free); + uring_task_set_buf(&proxy->clienttask, &proxy->clientbuf); + uring_task_set_fd(&proxy->clienttask, fd); + + uring_task_init(&proxy->servertask, "proxy_server", &proxy->task, + proxy_server_free); + uring_task_set_buf(&proxy->servertask, &proxy->serverbuf); + + list_add(&proxy->list, &server->proxys); + + if (server->state != SERVER_STATE_RUNNING) { + if (server_start(server) && + cfg->proxy_connection_interval > 0 && + cfg->proxy_connection_attempts > 0) { + ptask_init(&proxy->ptask, + cfg->proxy_connection_interval, + cfg->proxy_connection_attempts, + proxy_connect_timer_cb); + ptimer_add_task(&proxy->ptask); + } + } + + proxy->connecting = true; + connect_any(&proxy->servertask, &server->remotes, + &proxy->server_conn, proxy_connected_cb); + + return proxy; + +out_close_cpipe: + uring_close(&server->task, proxy->cpipe[PIPE_RD]); + uring_close(&server->task, proxy->cpipe[PIPE_WR]); +out_free: + xfree(proxy); +out: + return NULL; +} + +static void +local_accept(struct uring_task *task, int res) +{ + struct server_local *local = container_of(task, struct server_local, task); + struct server *server = container_of(task->parent, struct server, task); + struct server_proxy *proxy; + + assert_return(task); + assert_task_alive(DBG_PROXY, task); + + debug(DBG_PROXY, "task %p, res %i, server %s", task, res, server->name); + + if (res < 0) { + error("res: %i", res); + goto out; + } + + saddr_set_addrstr(&local->client); + + verbose("%s: incoming proxy connection: %s -> %s", + server->name, local->client.addrstr, local->local.addrstr); + + if (list_empty(&server->remotes)) { + /* This shouldn't be possible, checked before opening local */ + error("server->remotes empty!"); + uring_close(&local->task, res); + goto out; + } + + proxy = proxy_new(server, &local->client, res); + if (!proxy) + uring_close(&local->task, res); + +out: + uring_accept(&local->task, &local->client, local_accept); +} + +bool +local_open(struct server_local *local) +{ + int sfd; + int option; + int r; + + assert_return(local && local->server, false); + + sfd = socket(local->local.storage.ss_family, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (sfd < 0) { + error("socket: %m"); + goto error; + } + + option = true; + if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)) < 0) { + error("setsockopt: %m"); + goto error; + } + + /* The MC protocol expects the client to send data first */ + if (cfg->socket_defer) { + option = true; + if (setsockopt(sfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &option, sizeof(option)) < 0) + error("setsockopt: %m"); + } + + /* + * This has the advantage that interfaces don't need to be up but + * it means that cfg errors will not be caught. + */ + if (cfg->socket_freebind) { + option = true; + if (setsockopt(sfd, IPPROTO_IP, IP_FREEBIND, &option, sizeof(option)) < 0) + error("setsockopt: %m"); + } + + socket_set_low_latency(sfd); + + r = bind(sfd, (struct sockaddr *)&local->local.storage, local->local.addrlen); + if (r < 0) { + error("bind: %m"); + goto error; + } + + r = listen(sfd, 100); + if (r < 0) { + error("listen: %m"); + goto error; + } + + uring_task_set_fd(&local->task, sfd); + uring_accept(&local->task, &local->client, local_accept); + return true; + +error: + if (sfd >= 0) + uring_close(&local->task, sfd); + return false; +} + +void +local_refdump(struct server_local *local) +{ + assert_return(local); + + uring_task_refdump(&local->task); +} + +static void +local_free(struct uring_task *task) +{ + struct server_local *local = container_of(task, struct server_local, task); + + assert_return(task); + + debug(DBG_PROXY, "task %p, local %p", task, local); + list_del(&local->list); + xfree(local); +} + +void +local_delete(struct server_local *local) +{ + assert_return(local); + + uring_task_destroy(&local->task); +} + +struct server_local * +local_new(struct server *server, struct saddr *saddr) +{ + struct server_local *local; + + assert_return(server && saddr, NULL); + + local = zmalloc(sizeof(*local)); + if (!local) { + error("malloc: %m"); + return NULL; + } + + debug(DBG_PROXY, "%s adding local: %s", server->name, saddr->addrstr); + local->local = *saddr; + local->server = server; + uring_task_init(&local->task, "local", &server->task, local_free); + xfree(saddr); + return local; +} + diff --git a/mcserverproxy/server-proxy.h b/mcserverproxy/server-proxy.h new file mode 100644 index 0000000..ee3bda3 --- /dev/null +++ b/mcserverproxy/server-proxy.h @@ -0,0 +1,51 @@ +#ifndef fooserverproxyhfoo +#define fooserverproxyhfoo + +struct server_proxy { + struct connection client_conn; + struct uring_task_buf clientbuf; + struct uring_task clienttask; + uint64_t client_bytes; + int cpipe[2]; + int cfd; + + struct connection server_conn; + struct uring_task_buf serverbuf; + struct uring_task servertask; + uint64_t server_bytes; + int spipe[2]; + int sfd; + + bool connecting; + time_t begin; + struct ptimer_task ptask; + struct uring_task task; + struct server *server; + struct list_head list; +}; + +void proxy_refdump(struct server_proxy *proxy); + +void proxy_delete(struct server_proxy *proxy); + +struct server_proxy *proxy_new(struct server *server, struct saddr *client, + int fd); + +struct server_local { + struct saddr local; + struct saddr client; + struct uring_task task; + + struct server *server; + struct list_head list; +}; + +bool local_open(struct server_local *local); + +void local_refdump(struct server_local *local); + +void local_delete(struct server_local *local); + +struct server_local *local_new(struct server *server, struct saddr *saddr); + +#endif diff --git a/mcserverproxy/server-rcon.c b/mcserverproxy/server-rcon.c new file mode 100644 index 0000000..1f8ef70 --- /dev/null +++ b/mcserverproxy/server-rcon.c @@ -0,0 +1,227 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "uring.h" +#include "server.h" +#include "server-rcon.h" +#include "rcon-protocol.h" + +static int +rcon_packet_complete(struct uring_task *task, int res) +{ + assert_return(task, -EINVAL); + assert_task_alive_or(DBG_RCON, task, return -EINTR); + + return rcon_protocol_packet_complete(task->tbuf->buf, task->tbuf->len); +} + +static bool +rcon_check_reply(struct uring_task *task, int res, int32_t *id, + int32_t *type, const char **msg) +{ + struct server *server = container_of(task, struct server, rcon_task); + const char *error; + + if (res < 0) { + error("rcon(%s): reading reply failed, res: %i", + server->name, res); + goto error; + } + + if (!rcon_protocol_read_packet(task->tbuf->buf, task->tbuf->len, + id, type, msg, &error)) { + error("rcon(%s): failed to parse packet: %s", + server->name, error); + goto error; + } + + return true; + +error: + uring_task_close_fd(task); + return false; +} + +static void +rcon_stop_reply(struct uring_task *task, int res) +{ + struct server *server = container_of(task, struct server, rcon_task); + int32_t id; + int32_t type; + const char *msg; + + assert_return(task); + assert_task_alive(DBG_RCON, task); + + if (!rcon_check_reply(task, res, &id, &type, &msg)) + return; + + if (id != 2) { + error("rcon(%s): stop cmd failed, reply id (%" PRIi32 ")", + server->name, id); + goto out; + } else if (type != RCON_PACKET_RESPONSE) { + error("rcon(%s): stop cmd failed, reply type (%" PRIi32 ")", + server->name, type); + goto out; + } + + verbose("rcon(%s): stop command sent, reply: %s", server->name, msg); + +out: + uring_task_close_fd(task); +} + +static void +rcon_stop_sent(struct uring_task *task, int res) +{ + struct server *server = container_of(task, struct server, rcon_task); + + assert_return(task); + assert_task_alive(DBG_RCON, task); + + if (res != task->tbuf->len) { + error("rcon(%s): sending stop cmd failed, res: %i", + server->name, res); + uring_task_close_fd(task); + return; + } + + debug(DBG_RCON, "rcon(%s): stop cmd sent", server->name); + uring_tbuf_read_until(task, rcon_packet_complete, rcon_stop_reply); +} + +static void +rcon_login_reply(struct uring_task *task, int res) +{ + struct server *server = container_of(task, struct server, rcon_task); + int32_t id; + int32_t type; + const char *msg; + + assert_return(task); + assert_task_alive(DBG_RCON, task); + + if (!rcon_check_reply(task, res, &id, &type, &msg)) + return; + + if (id != 1) { + error("rcon(%s): login failed, reply id (%" PRIi32 ")", + server->name, id); + goto error; + } else if (type == RCON_PACKET_LOGIN_FAIL) { + error("rcon(%s): login failed, incorrect password", + server->name); + goto error; + } else if (type != RCON_PACKET_LOGIN_OK) { + error("rcon(%s): login failed, reply type (%" PRIi32 ")", + server->name, type); + goto error; + } + + debug(DBG_RCON, "rcon(%s): login successful", server->name); + rcon_protocol_create_packet(task->tbuf->buf, sizeof(task->tbuf->buf), + &task->tbuf->len, 2, RCON_PACKET_COMMAND, + "stop"); + uring_tbuf_write(task, rcon_stop_sent); + return; + +error: + uring_task_close_fd(task); +} + +static void +rcon_login_sent(struct uring_task *task, int res) +{ + struct server *server = container_of(task, struct server, rcon_task); + + assert_return(task); + assert_task_alive(DBG_RCON, task); + + if (res != task->tbuf->len) { + error("rcon(%s): sending login failed, res: %i", + server->name, res); + uring_task_close_fd(task); + return; + } + + debug(DBG_RCON, "rcon(%s): login sent", server->name); + uring_tbuf_read_until(task, rcon_packet_complete, rcon_login_reply); +} + +static void +rcon_connected_cb(struct connection *conn, bool connected) +{ + struct server *server = container_of(conn, struct server, rcon_conn); + + assert_return(conn); + assert_task_alive(DBG_RCON, &server->rcon_task); + + if (!connected) { + error("rcon (%s): connection failed", server->name); + return; + } + + rcon_protocol_create_packet(server->rcon_tbuf.buf, + sizeof(server->rcon_tbuf.buf), + &server->rcon_tbuf.len, 1, + RCON_PACKET_LOGIN, + server->rcon_password); + uring_tbuf_write(&server->rcon_task, rcon_login_sent); +} + +static void +rcon_free(struct uring_task *task) +{ + struct server *server = container_of(task, struct server, rcon_task); + + assert_return(task); + + debug(DBG_RCON, "task %p, server %s (%p)", task, server->name, server); +} + +void +rcon_stop(struct server *server) +{ + assert_return(server && !list_empty(&server->rcons) && !empty_str(server->rcon_password)); + assert_task_alive(DBG_RCON, &server->rcon_task); + + connect_any(&server->rcon_task, &server->rcons, &server->rcon_conn, rcon_connected_cb); +} + +void +rcon_refdump(struct server *server) +{ + assert_return(server); + + uring_task_refdump(&server->rcon_task); +} + +void +rcon_delete(struct server *server) +{ + assert_return(server); + + debug(DBG_RCON, "closing fd %i", server->rcon_task.fd); + uring_task_destroy(&server->rcon_task); +} + +void +rcon_init(struct server *server) +{ + assert_return(server); + + uring_task_init(&server->rcon_task, "rcon", &server->task, rcon_free); + uring_task_set_buf(&server->rcon_task, &server->rcon_tbuf); +} + diff --git a/mcserverproxy/server-rcon.h b/mcserverproxy/server-rcon.h new file mode 100644 index 0000000..6625f25 --- /dev/null +++ b/mcserverproxy/server-rcon.h @@ -0,0 +1,12 @@ +#ifndef fooserverrconhfoo +#define fooserverrconhfoo + +void rcon_stop(struct server *server); + +void rcon_refdump(struct server *server); + +void rcon_delete(struct server *server); + +void rcon_init(struct server *server); + +#endif diff --git a/mcserverproxy/server.c b/mcserverproxy/server.c new file mode 100644 index 0000000..de42721 --- /dev/null +++ b/mcserverproxy/server.c @@ -0,0 +1,837 @@ +#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 "utils.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; +} diff --git a/mcserverproxy/server.h b/mcserverproxy/server.h new file mode 100644 index 0000000..ff4c28e --- /dev/null +++ b/mcserverproxy/server.h @@ -0,0 +1,128 @@ +#ifndef fooserverhfoo +#define fooserverhfoo + +enum server_state { + SERVER_STATE_INIT = 0, + SERVER_STATE_CFG_OK = 1, + SERVER_STATE_RUNNING = 2, + SERVER_STATE_STOPPED = 3, + SERVER_STATE_DEAD = 4, +}; + +enum server_type { + SERVER_TYPE_UNDEFINED, + SERVER_TYPE_ANNOUNCE, + SERVER_TYPE_PROXY, +}; + +enum server_stop_method { + SERVER_STOP_METHOD_UNDEFINED, + SERVER_STOP_METHOD_RCON, + SERVER_STOP_METHOD_SYSTEMD, + SERVER_STOP_METHOD_EXEC, +}; + +enum server_start_method { + SERVER_START_METHOD_UNDEFINED, + SERVER_START_METHOD_SYSTEMD, + SERVER_START_METHOD_EXEC, +}; + +struct server { + enum server_type type; + char *name; + char *pretty_name; + uint16_t announce_port; + struct list_head locals; + struct list_head remotes; + struct list_head proxys; + struct list_head rcons; + struct list_head dnslookups; + enum server_state state; + + enum server_stop_method stop_method; + enum server_start_method start_method; + + /* For calling external start/stop executables */ + char *stop_exec; + char *start_exec; + struct uring_task exec_task; + + /* For systemd services */ + char *systemd_service; + char *systemd_obj; + + /* For rcon connections */ + char *rcon_password; + struct connection rcon_conn; + struct uring_task rcon_task; + struct uring_task_buf rcon_tbuf; + + /* For announce messages */ + struct uring_task ann_task; + struct uring_task_buf ann_buf; + + /* For checking idle status */ + struct uring_task idle_task; + struct connection idle_conn; + struct uring_task_buf idle_buf; + unsigned idle_timeout; + unsigned idle_count; + + /* For reading config files */ + struct uring_task task; + struct uring_task_buf tbuf; + + struct list_head list; +}; + +void server_refdump(struct server *server); + +void server_delete(struct server *server); + +void server_delete_by_name(const char *name); + +bool server_start(struct server *server); + +bool server_stop(struct server *server); + +void server_set_active_players(struct server *server, int count); + +bool server_idle_check(struct server *server); + +bool server_announce(struct server *server, int fd); + +bool server_commit(struct server *server); + +bool server_add_remote(struct server *server, struct saddr *remote); + +bool server_add_local(struct server *server, struct saddr *saddr); + +bool server_add_rcon(struct server *server, struct saddr *rcon); + +bool server_set_rcon_password(struct server *server, const char *password); + +bool server_set_systemd_service(struct server *server, const char *service); + +bool server_set_stop_method(struct server *server, + enum server_stop_method stop_method); + +bool server_set_start_method(struct server *server, + enum server_start_method start_method); + +bool server_set_stop_exec(struct server *server, const char *cmd); + +bool server_set_start_exec(struct server *server, const char *cmd); + +bool server_set_idle_timeout(struct server *server, uint16_t timeout); + +bool server_set_port(struct server *server, uint16_t port); + +bool server_set_type(struct server *server, enum server_type type); + +bool server_set_pretty_name(struct server *server, const char *pretty_name); + +struct server *server_new(const char *name); + +#endif + diff --git a/mcserverproxy/signal-handler.c b/mcserverproxy/signal-handler.c new file mode 100644 index 0000000..67c2e0b --- /dev/null +++ b/mcserverproxy/signal-handler.c @@ -0,0 +1,195 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "main.h" +#include "signal-handler.h" +#include "uring.h" +#include "server.h" +#include "server-config.h" +#include "config-parser.h" +#include "igmp.h" +#include "announce.h" +#include "idle.h" +#include "ptimer.h" + +struct signal_ev { + struct uring_task task; + struct uring_task_buf tbuf; + int pipe[2]; +}; + +static void +signal_delete() +{ + assert_return(cfg->signal); + + uring_task_destroy(&cfg->signal->task); +} + +static void +signalfd_read(struct uring_task *task, int res) +{ + struct signal_ev *signal = container_of(task, struct signal_ev, task); + struct server *server, *stmp; + static unsigned count = 0; + siginfo_t *si; + + assert_return(task); + assert_task_alive(DBG_SIG, task); + + si = (siginfo_t *)task->tbuf->buf; + if (res != sizeof(*si)) + die("error in signalfd (%i)", res); + + switch (si->si_signo) { + case SIGUSR1: { + struct dns_async *dns; + + debug(DBG_SIG, "Got a SIGUSR1"); + if (si->si_code != SI_ASYNCNL || !si->si_ptr) { + error("SIGUSR1: unexpected values in siginfo"); + goto out; + } + + dns = si->si_ptr; + if (!dns->cb) { + error("DNS callback not set"); + goto out; + } + + debug(DBG_DNS, "DNS lookup complete, dns: %p, dns->cb: %p", + dns, dns->cb); + dns->cb(dns); + break; + } + + case SIGUSR2: + debug(DBG_SIG, "got a SIGUSR2"); + dump_tree(); + break; + + case SIGTERM: + debug(DBG_SIG, "Got a SIGINT/SIGHUP"); + verbose("got a signal to quit"); + sd_notifyf(0, "STOPPING=1\nSTATUS=Received signal, exiting"); + exit(EXIT_SUCCESS); + break; + + case SIGINT: + case SIGHUP: + count++; + if (count > 5) { + dump_tree(); + exit(EXIT_FAILURE); + } + + verbose("got a signal to dump tree"); + sd_notifyf(0, "STOPPING=1\nSTATUS=Received signal, exiting"); + dump_tree(); + signal_delete(); + ptimer_delete(); + igmp_delete(); + announce_delete(); + idle_delete(); + server_cfg_monitor_delete(); + list_for_each_entry_safe(server, stmp, &cfg->servers, list) + server_delete(server); + uring_delete(); + return; + + default: + error("got an unknown signal: %i", si->si_signo); + break; + } + +out: + uring_tbuf_read(&signal->task, signalfd_read); +} + +static void +hack_signal_handler(int signum, siginfo_t *si, void *ucontext) +{ + ssize_t r; + + assert_return(signum > 0 && si); + + r = write(cfg->signal->pipe[PIPE_WR], si, sizeof(*si)); + if (r != sizeof(*si)) + error("write: %zi\n", r); + +} + +static void +signal_free(struct uring_task *task) +{ + struct signal_ev *signal = container_of(task, struct signal_ev, task); + + assert_return(task); + + debug(DBG_SIG, "called"); + close(signal->pipe[PIPE_WR]); + cfg->signal = NULL; + xfree(signal); +} + +void +signal_refdump() +{ + assert_return(cfg->signal); + + uring_task_refdump(&cfg->signal->task); +} + +void +signal_init() +{ + //sigset_t mask; + struct signal_ev *signal; + + assert_return(!cfg->signal); + + signal = zmalloc(sizeof(*signal)); + if (!signal) + die("malloc: %m"); + + if (pipe2(signal->pipe, O_CLOEXEC) < 0) + die("pipe2: %m"); + + /* + sigfillset(&mask); + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) + die("sigprocmask: %m"); + + sfd = signalfd(-1, &mask, SFD_CLOEXEC); + if (sfd < 0) + die("signalfd: %m"); + */ + + struct sigaction action; + sigfillset(&action.sa_mask); + action.sa_sigaction = hack_signal_handler; + action.sa_flags = SA_SIGINFO; + + sigaction(SIGINT, &action, NULL); + sigaction(SIGHUP, &action, NULL); + sigaction(SIGTERM, &action, NULL); + sigaction(SIGUSR1, &action, NULL); + sigaction(SIGUSR2, &action, NULL); + + action.sa_handler = SIG_IGN; + action.sa_flags = 0; + sigaction(SIGPIPE, &action, NULL); + + debug(DBG_SIG, "using pipe fds %i -> %i", + signal->pipe[PIPE_WR], signal->pipe[PIPE_RD]); + uring_task_init(&signal->task, "signal", uring_parent(), signal_free); + uring_task_set_fd(&signal->task, signal->pipe[PIPE_RD]); + uring_task_set_buf(&signal->task, &signal->tbuf); + cfg->signal = signal; + uring_tbuf_read(&signal->task, signalfd_read); +} + diff --git a/mcserverproxy/signal-handler.h b/mcserverproxy/signal-handler.h new file mode 100644 index 0000000..e0140b3 --- /dev/null +++ b/mcserverproxy/signal-handler.h @@ -0,0 +1,8 @@ +#ifndef foosignalhfoo +#define foosignalhfoo + +void signal_refdump(); + +void signal_init(); + +#endif diff --git a/mcserverproxy/systemd.c b/mcserverproxy/systemd.c new file mode 100644 index 0000000..a44b0d8 --- /dev/null +++ b/mcserverproxy/systemd.c @@ -0,0 +1,219 @@ +#include +#include +#include + +#include "main.h" +#include "server.h" +#include "systemd.h" + +#define SYSTEMD_DBUS_SERVICE "org.freedesktop.systemd1" +#define SYSTEMD_DBUS_INTERFACE "org.freedesktop.systemd1.Unit" +#define SYSTEMD_DBUS_PATH_PREFIX "/org/freedesktop/systemd1/unit/" + +static inline char +tohex(uint8_t val) +{ + static const char hex[] = "0123456789abcdef"; + + return hex[val & 0x0f]; +} + +/* + * Creates an escaped D-Bus object path for a given systemd service + * + * Escaping rules are documented here: + * https://dbus.freedesktop.org/doc/dbus-specification.html + * + * Essentially, everyting but a-z, A-Z, 0-9 is replaced by _xx where xx is + * the hexadecimal value of the character. + * + * Example: minecraft@world1.service -> minecraft_40world1_2eservice + */ +char * +systemd_object_path(const char *service) +{ + char *r; + char *d; + const char *s; + + assert_return(service && !empty_str(service), NULL); + + r = zmalloc(strlen(SYSTEMD_DBUS_PATH_PREFIX) + strlen(service) * 3 + 1); + if (!r) + return NULL; + + memcpy(r, SYSTEMD_DBUS_PATH_PREFIX, strlen(SYSTEMD_DBUS_PATH_PREFIX)); + d = r + strlen(SYSTEMD_DBUS_PATH_PREFIX); + + for (s = service; *s; s++) { + if ((*s >= 'a' && *s <= 'z') || + (*s >= 'A' && *s <= 'Z') || + (*s >= '0' && *s <= '9')) { + *(d++) = *s; + continue; + } + + *(d++) = '_'; + *(d++) = tohex(*s >> 4); + *(d++) = tohex(*s); + } + + *d = '\0'; + return r; +} + +void +systemd_delete() +{ + assert_return_silent(cfg->sd_bus); + + sd_bus_unref(cfg->sd_bus); + cfg->sd_bus = NULL; +} + +static sd_bus * +get_bus() +{ + int r; + + if (cfg->sd_bus_failed) + return NULL; + + if (!cfg->sd_bus) { + r = sd_bus_open_user(&cfg->sd_bus); + if (r < 0) { + error("failed to connect to user system bus: %s", strerror(-r)); + cfg->sd_bus_failed = true; + return NULL; + } + } + + return cfg->sd_bus; +} + +/* + * Check if a systemd service is running. + * + * This is equivalent to (assuming service minecraft@world1): + * gdbus call --session + * --dest org.freedesktop.systemd1 + * --object-path /org/freedesktop/systemd1/unit/minecraft_40world1_2eservice + * --method org.freedesktop.DBus.Properties.Get + * "org.freedesktop.systemd1.Unit" + * "ActiveState" + */ +bool +systemd_service_running(struct server *server) +{ + sd_bus *bus = get_bus(); + sd_bus_error error = SD_BUS_ERROR_NULL; + char *status = NULL; + bool running = false; + int r; + + assert_return(server && bus && server->systemd_service && server->systemd_obj, false); + + r = sd_bus_get_property_string(bus, + SYSTEMD_DBUS_SERVICE, + server->systemd_obj, + SYSTEMD_DBUS_INTERFACE, + "ActiveState", + &error, + &status); + if (r < 0) { + error("failed to get status for service %s (%s): %s", + server->systemd_service, server->systemd_obj, error.message); + goto out; + } + + if (streq(status, "active")) { + running = true; + debug(DBG_SYSD, "systemd service %s (%s) is active", + server->systemd_service, server->systemd_obj); + } else + debug(DBG_SYSD, "systemd service %s (%s) is not active", + server->systemd_service, server->systemd_obj); + +out: + free(status); + sd_bus_error_free(&error); + return running; +} + +static bool +systemd_service_action(struct server *server, const char *action) +{ + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *m = NULL; + sd_bus *bus = get_bus(); + const char *path; + bool performed = false; + int r; + + assert_return(server && bus && server->systemd_service && server->systemd_obj && action, false); + + r = sd_bus_call_method(bus, + SYSTEMD_DBUS_SERVICE, + server->systemd_obj, + SYSTEMD_DBUS_INTERFACE, + action, + &error, + &m, + "s", + "fail"); + if (r < 0) { + error("failed to perform action %s on systemd service %s: %s", + action, server->systemd_service, error.message); + goto out; + } + + r = sd_bus_message_read(m, "o", &path); + if (r < 0) { + error("failed to parse response message: %s", strerror(-r)); + goto out; + } + + verbose("action %s queued for service %s", + action, server->systemd_service); + performed = true; + +out: + sd_bus_error_free(&error); + sd_bus_message_unref(m); + return performed; +} + +/* + * Stop systemd service. + * + * This is equivalent to (assuming service minecraft@world1): + * gdbus call --session + * --dest org.freedesktop.systemd1 + * --object-path /org/freedesktop/systemd1/unit/minecraft_40world1_2eservice + * --method org.freedesktop.systemd1.Unit.Stop "fail" + */ +bool +systemd_service_stop(struct server *server) +{ + assert_return(server, false); + + return systemd_service_action(server, "Stop"); +} + +/* + * Start systemd service. + * + * This is equivalent to (assuming service minecraft@world1): + * gdbus call --session + * --dest org.freedesktop.systemd1 + * --object-path /org/freedesktop/systemd1/unit/minecraft_40world1_2eservice + * --method org.freedesktop.systemd1.Unit.Start "fail" + */ +bool +systemd_service_start(struct server *server) +{ + assert_return(server, false); + + return systemd_service_action(server, "Start"); +} + diff --git a/mcserverproxy/systemd.h b/mcserverproxy/systemd.h new file mode 100644 index 0000000..d455044 --- /dev/null +++ b/mcserverproxy/systemd.h @@ -0,0 +1,14 @@ +#ifndef foosystemdhfoo +#define foosystemdhfoo + +char *systemd_object_path(const char *service); + +void systemd_delete(); + +bool systemd_service_running(struct server *server); + +bool systemd_service_stop(struct server *server); + +bool systemd_service_start(struct server *server); + +#endif diff --git a/mcserverproxy/uring.c b/mcserverproxy/uring.c new file mode 100644 index 0000000..e979471 --- /dev/null +++ b/mcserverproxy/uring.c @@ -0,0 +1,759 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "uring.h" + +struct uring_ev { + struct io_uring uring; + struct io_uring_params uring_params; + struct uring_task task; + + /* for testing if the kernel supports splice */ + int pipe[2]; + int tfd; +}; + +enum cqe_type { + CQE_TYPE_NORMAL = 0x0, + CQE_TYPE_CANCEL = 0x1, + CQE_TYPE_CLOSE = 0x2, + CQE_TYPE_POLL_CANCEL = 0x3 +}; + +#define CQE_TYPE_PTR_MASK 0x3 + +uint64_t sqe_count = 0; +uint64_t cqe_count = 0; + +static struct io_uring_sqe * +get_sqe(struct uring_task *task) +{ + struct io_uring_sqe *sqe; + + assert_die(task, "invalid arguments"); + + sqe = io_uring_get_sqe(&cfg->uring->uring); + if (!sqe) { + io_uring_submit(&cfg->uring->uring); + sqe = io_uring_get_sqe(&cfg->uring->uring); + if (!sqe) + die("failed to get an sqe!"); + } + + sqe_count++; + uring_task_get(task); + return sqe; +} + +void +uring_task_refdump(struct uring_task *task) +{ + char buf[4096]; + struct uring_task *tmp; + + assert_return(task); + + if (!debug_enabled(DBG_REF)) + return; + + buf[0] = '\0'; + for (tmp = task; tmp; tmp = tmp->parent) { + size_t prefix; + char *dst; + + if (tmp->parent) + prefix = strlen("->") + strlen(tmp->name); + else + prefix = strlen(tmp->name); + + memmove(&buf[prefix], &buf[0], strlen(buf) + 1); + + dst = &buf[0]; + if (tmp->parent) { + *dst++ = '-'; + *dst++ = '>'; + } + + memcpy(dst, tmp->name, strlen(tmp->name)); + } + + info("%s (0x%p parent 0x%p free 0x%p fd %i ref %u)", + buf, task, task->parent, task->free, task->fd, + task->refcount); +} + +/* + * Similar to uring_task_put, but can be called from other tasks + * while the task is active. + */ +void +uring_task_destroy(struct uring_task *task) +{ + assert_return(task); + assert_return_silent(!task->dead); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + if (task->fd >= 0) { + struct io_uring_sqe *sqe; + + sqe = get_sqe(task); + io_uring_prep_cancel(sqe, task, 0); + io_uring_sqe_set_data(sqe, (void *)((uintptr_t)task | CQE_TYPE_CANCEL)); + } + + task->dead = true; + uring_task_put(task); +} + +void +uring_task_put(struct uring_task *task) +{ + struct uring_task *parent; + + assert_return(task); + + debug(DBG_REF, "task %s (%p), refcount %u -> %u", + task->name, task, task->refcount, task->refcount - 1); + + task->refcount--; + + if (task->refcount > 0) + return; + + if (task->refcount < 0) + error("Negative refcount!"); + + if (task->fd >= 0) { + uring_task_close_fd(task); + /* We'll be called again once the fd is closed */ + return; + } + + parent = task->parent; + if (task->free) + task->free(task); + + if (parent) { + debug(DBG_REF, "putting parent %s (%p)", parent->name, parent); + uring_task_put(parent); + } +} + +void +uring_task_get(struct uring_task *task) +{ + assert_return(task); + + debug(DBG_REF, "task %s (%p), refcount %u -> %u", + task->name, task, task->refcount, task->refcount + 1); + + if (task->refcount < 0) + error("Negative refcount!"); + + task->refcount++; +} + +void +uring_task_set_buf(struct uring_task *task, struct uring_task_buf *tbuf) +{ + assert_return(task && tbuf); + + debug(DBG_UR, "task %s (%p), buf %p, refcount %u", + task->name, task, tbuf, task->refcount); + + /* iov_len and msg_namelen are set at send/receive time */ + tbuf->iov.iov_base = tbuf->buf; + tbuf->msg.msg_name = &task->saddr.storage; + tbuf->msg.msg_iov = &tbuf->iov; + tbuf->msg.msg_iovlen = 1; + tbuf->msg.msg_control = NULL; + tbuf->msg.msg_controllen = 0; + tbuf->msg.msg_flags = 0; + task->tbuf = tbuf; +} + +void +uring_task_set_fd(struct uring_task *task, int fd) +{ + assert_return(task); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, fd, task->refcount); + + task->fd = fd; +} + +void +uring_task_close_fd(struct uring_task *task) +{ + assert_return(task); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + if (task->fd < 0) + return; + + uring_close(task, task->fd); + task->fd = -1; +} + +struct uring_task * +uring_parent() +{ + assert_die(cfg->uring, "invalid arguments"); + + return &cfg->uring->task; +} + +void +uring_task_init(struct uring_task *task, const char *name, + struct uring_task *parent, void (*free)(struct uring_task *)) +{ + static bool first = true; + + assert_die(task && !empty_str(name) && free, "invalid arguments"); + + if (first) + first = false; + else + assert_die(parent, "called without a parent task"); + + task->refcount = 1; + task->fd = -1; + task->parent = parent; + task->free = free; + task->dead = false; + task->name = name; + task->tbuf = NULL; + + if (task->parent) { + debug(DBG_REF, "task %s (%p), refcount %u, " + "getting parent %s (%p), refcount %u", + task->name, task, task->refcount, + task->parent->name, task->parent, task->parent->refcount); + uring_task_get(task->parent); + } +} + +void +uring_close(struct uring_task *task, int fd) +{ + struct io_uring_sqe *sqe; + + assert_return(task && fd >= 0); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + sqe = get_sqe(task); + io_uring_prep_close(sqe, fd); + io_uring_sqe_set_data(sqe, (void *)((uintptr_t)task | CQE_TYPE_CLOSE)); +} + +static void +uring_tbuf_write_cb(struct uring_task *task, int res) +{ + int r; + + assert_return(task && task->tbuf && task->final_cb); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + if (res < 0) { + r = res; + goto finished; + } + + /* We wrote some more data */ + task->tbuf->done += res; + if (task->tbuf->done >= task->tbuf->len || res == 0) { + r = task->tbuf->len; + goto finished; + } + + uring_write(task, task->tbuf->buf + task->tbuf->done, + task->tbuf->len - task->tbuf->done, + uring_tbuf_write_cb); + return; + +finished: + task->final_cb(task, r); +} + +void +uring_tbuf_write(struct uring_task *task, utask_cb_t final_cb) +{ + assert_return(task && task->fd >= 0 && task->tbuf && task->tbuf->len > 0); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + task->tbuf->done = 0; + task->final_cb = final_cb; + uring_write(task, &task->tbuf->buf, task->tbuf->len, uring_tbuf_write_cb); +} + +void +uring_write(struct uring_task *task, void *buf, size_t len, utask_cb_t cb) +{ + struct io_uring_sqe *sqe; + + assert_return(task && buf && len > 0 && cb && task->fd >= 0); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + sqe = get_sqe(task); + task->cb = cb; + io_uring_prep_write(sqe, task->fd, buf, len, 0); + io_uring_sqe_set_data(sqe, task); +} + +static void +uring_tbuf_read_until_cb(struct uring_task *task, int res) +{ + int r; + + assert_return(task && task->tbuf && task->final_cb && task->is_complete_cb); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + if (res < 0) { + r = res; + goto finished; + } + + task->tbuf->len += res; + r = task->is_complete_cb(task, res); + if (r < 0) { + r = res; + goto finished; + } else if (r > 0) { + r = task->tbuf->len; + goto finished; + } + + /* Need to read more */ + if (task->tbuf->len >= sizeof(task->tbuf->buf)) { + r = E2BIG; + goto finished; + } + + uring_read_offset(task, task->tbuf->buf + task->tbuf->len, + sizeof(task->tbuf->buf) - task->tbuf->len, + task->tbuf->len, uring_tbuf_read_until_cb); + return; + +finished: + task->final_cb(task, r); +} + +void +uring_tbuf_read_until(struct uring_task *task, + rutask_cb_t is_complete_cb, utask_cb_t final_cb) +{ + assert_return(task && task->fd >= 0 && task->tbuf && is_complete_cb && final_cb); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + task->tbuf->len = 0; + task->is_complete_cb = is_complete_cb; + task->final_cb = final_cb; + uring_read(task, &task->tbuf->buf, sizeof(task->tbuf->buf), + uring_tbuf_read_until_cb); +} + +static int +uring_tbuf_eof(struct uring_task *task, int res) +{ + assert_return(task && task->tbuf, -EINVAL); + assert_task_alive_or(DBG_UR, task, return -EINTR); + + if (task->tbuf->len + 1 >= sizeof(task->tbuf->buf)) + return -E2BIG; + + if (res > 0) + return 0; + + task->tbuf->buf[task->tbuf->len] = '\0'; + task->tbuf->len++; + return 1; +} + +void +uring_tbuf_read_until_eof(struct uring_task *task, + utask_cb_t final_cb) +{ + assert_return(task && task->tbuf && final_cb); + + uring_tbuf_read_until(task, uring_tbuf_eof, final_cb); +} + +static int +uring_tbuf_have_data(struct uring_task *task, int res) +{ + assert_return(task, -EINVAL); + + if (res < 0) + return res; + else + return 1; +} + +void +uring_tbuf_read(struct uring_task *task, utask_cb_t final_cb) +{ + assert_return(task && final_cb); + + uring_tbuf_read_until(task, uring_tbuf_have_data, final_cb); +} + +void +uring_read_offset(struct uring_task *task, void *buf, size_t len, off_t offset, utask_cb_t cb) +{ + struct io_uring_sqe *sqe; + + assert_return(task && buf && len > 0 && task->fd >= 0); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + sqe = get_sqe(task); + task->cb = cb; + io_uring_prep_read(sqe, task->fd, buf, len, offset); + io_uring_sqe_set_data(sqe, task); +} + +void +uring_openat(struct uring_task *task, const char *path, utask_cb_t cb) +{ + struct io_uring_sqe *sqe; + + assert_return(task && !empty_str(path) && cb); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + sqe = get_sqe(task); + task->cb = cb; + io_uring_prep_openat(sqe, AT_FDCWD, path, O_RDONLY | O_CLOEXEC, 0); + io_uring_sqe_set_data(sqe, task); +} + +void +uring_tbuf_recvmsg(struct uring_task *task, utask_cb_t cb) +{ + struct io_uring_sqe *sqe; + + assert_return(task && task->fd >= 0 && task->tbuf && cb); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + sqe = get_sqe(task); + task->tbuf->done = 0; + task->tbuf->len = 0; + task->tbuf->iov.iov_len = sizeof(task->tbuf->buf); + task->tbuf->msg.msg_namelen = task->saddr.addrlen; + task->cb = cb; + io_uring_prep_recvmsg(sqe, task->fd, &task->tbuf->msg, 0); + io_uring_sqe_set_data(sqe, task); +} + +void +uring_tbuf_sendmsg(struct uring_task *task, utask_cb_t cb) +{ + struct io_uring_sqe *sqe; + + assert_return(task && task->fd >= 0 && task->tbuf && cb); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + sqe = get_sqe(task); + task->tbuf->done = 0; + task->tbuf->iov.iov_len = task->tbuf->len; + task->tbuf->msg.msg_namelen = task->saddr.addrlen; + task->cb = cb; + io_uring_prep_sendmsg(sqe, task->fd, &task->tbuf->msg, 0); + io_uring_sqe_set_data(sqe, task); +} + +void +uring_connect(struct uring_task *task, struct saddr *saddr, utask_cb_t cb) +{ + struct io_uring_sqe *sqe; + + assert_return(task && task->fd >= 0 && saddr && cb); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + sqe = get_sqe(task); + task->cb = cb; + io_uring_prep_connect(sqe, task->fd, (struct sockaddr *)&saddr->storage, saddr->addrlen); + io_uring_sqe_set_data(sqe, task); +} + +void +uring_accept(struct uring_task *task, struct saddr *saddr, utask_cb_t cb) +{ + struct io_uring_sqe *sqe; + + assert_return(task && task->fd >= 0 && saddr && cb); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + sqe = get_sqe(task); + saddr->addrlen = sizeof(saddr->storage); + task->cb = cb; + io_uring_prep_accept(sqe, task->fd, (struct sockaddr *)&saddr->storage, &saddr->addrlen, SOCK_CLOEXEC); + io_uring_sqe_set_data(sqe, task); +} + +void +uring_splice(struct uring_task *task, int fd_in, int fd_out, utask_cb_t cb) +{ + struct io_uring_sqe *sqe; + + assert_return(task && fd_in >= 0 && fd_out >= 0 && cb); + + debug(DBG_UR, "task %s (%p), fd_in %i, fd_out %i, refcount %u", + task->name, task, fd_in, fd_out, task->refcount); + + sqe = get_sqe(task); + task->cb = cb; + io_uring_prep_splice(sqe, fd_in, -1, fd_out, -1, 4096, SPLICE_F_MOVE); + io_uring_sqe_set_data(sqe, task); +} + +void +uring_poll(struct uring_task *task, short poll_mask, utask_cb_t cb) +{ + struct io_uring_sqe *sqe; + + assert_return(task && task->fd >= 0 && poll_mask && cb); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + sqe = get_sqe(task); + task->cb = cb; + io_uring_prep_poll_add(sqe, task->fd, poll_mask); + io_uring_sqe_set_data(sqe, task); +} + +void +uring_poll_cancel(struct uring_task *task) +{ + struct io_uring_sqe *sqe; + + assert_return(task); + + if (task->fd < 0) { + /* not an error, no need to print error msg */ + return; + } + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + sqe = get_sqe(task); + task->dead = true; + io_uring_prep_poll_remove(sqe, task); + io_uring_sqe_set_data(sqe, (void *)((uintptr_t)task | CQE_TYPE_POLL_CANCEL)); +} + +static void +uring_free(struct uring_task *task) +{ + struct uring_ev *uring = container_of(task, struct uring_ev, task); + + assert_return(task); + + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + io_uring_queue_exit(&uring->uring); + cfg->uring = NULL; + xfree(uring); +} + +void +uring_refdump() +{ + assert_return(cfg->uring); + + uring_task_refdump(&cfg->uring->task); +} + +void +uring_delete() +{ + struct uring_task *task; + + assert_return(cfg->uring); + + task = &cfg->uring->task; + debug(DBG_UR, "task %s (%p), fd %i, refcount %u", + task->name, task, task->fd, task->refcount); + + uring_task_put(task); +} + +static void +uring_splice_test_cb(struct uring_task *task, int res) +{ + struct uring_ev *uring = container_of(task, struct uring_ev, task); + + assert_die(task && uring == cfg->uring, "splice test failed"); + + uring_close(task, uring->tfd); + uring_close(task, uring->pipe[PIPE_RD]); + uring_close(task, uring->pipe[PIPE_WR]); + + uring->tfd = -1; + uring->pipe[PIPE_RD] = -1; + uring->pipe[PIPE_WR] = -1; + + if (res >= 0) { + cfg->splice_supported = true; + debug(DBG_UR, "splice supported"); + } else if (res == -EINVAL) + debug(DBG_UR, "splice not supported"); + else + error("splice check failed: %i\n", res); +} + +void +uring_init() +{ + struct uring_ev *uring; + + assert_return(!cfg->uring); + + uring = zmalloc(sizeof(*uring)); + if (!uring) + die("malloc: %m"); + + if (io_uring_queue_init_params(4096, &uring->uring, &uring->uring_params) < 0) + die("io_uring_queue_init_params"); + + debug(DBG_UR, "uring initialized, features: 0x%08x", + uring->uring_params.features); + + uring_task_init(&uring->task, "io_uring", &cfg->task, uring_free); + cfg->uring = uring; + + /* splice check, a bit convoluted, but seems to be no simpler way */ + cfg->splice_supported = false; + if (pipe2(uring->pipe, O_CLOEXEC) < 0) + die("pipe2: %m"); + uring->tfd = open("/dev/null", O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (uring->tfd < 0) + die("open(\"/dev/null\"): %m"); + uring_splice(&uring->task, uring->tfd, uring->pipe[PIPE_WR], uring_splice_test_cb); +} + +static inline void +uring_print_cqe(const char *type, struct uring_task *task, + struct io_uring_cqe *cqe) +{ + assert_return(!empty_str(type) && task && cqe); + + debug(DBG_UR, "got CQE " + "(type: %s, res: %i (%s), task: %s (%p), fd: %i, cb: %p)", + type, + cqe->res, + cqe->res < 0 ? strerror(-cqe->res) : "ok", + task->name ? task->name : "", + task, + task->fd, + task->cb); +} + +void +uring_event_loop() +{ + while (true) { + struct io_uring_cqe *cqe; + unsigned nr, head; + int r; + + io_uring_submit(&cfg->uring->uring); + r = io_uring_wait_cqe(&cfg->uring->uring, &cqe); + if (r < 0) { + if (errno == EINTR) + continue; + else + die("io_uring_wait_cqe: %i", r); + } + + nr = 0; + io_uring_for_each_cqe(&cfg->uring->uring, head, cqe) { + struct uring_task *task = io_uring_cqe_get_data(cqe); + bool do_cb; + enum cqe_type cqe_type; + + cqe_count++; + + cqe_type = ((uintptr_t)task & CQE_TYPE_PTR_MASK); + task = (void *)((uintptr_t)task & ~CQE_TYPE_PTR_MASK); + + if (!task) + die("null task"); + + switch (cqe_type) { + case CQE_TYPE_CANCEL: + uring_print_cqe("cancel", task, cqe); + do_cb = false; + break; + + case CQE_TYPE_CLOSE: + uring_print_cqe("close", task, cqe); + do_cb = false; + break; + + case CQE_TYPE_POLL_CANCEL: + uring_print_cqe("poll_cancel", task, cqe); + do_cb = false; + break; + + case CQE_TYPE_NORMAL: + uring_print_cqe("standard", task, cqe); + do_cb = true; + break; + + default: + die("unknown CQE type"); + } + + if (do_cb && task->cb) + task->cb(task, cqe->res); + + uring_task_put(task); + + if (exiting) + return; + + nr++; + } + + io_uring_cq_advance(&cfg->uring->uring, nr); + } +} + diff --git a/mcserverproxy/uring.h b/mcserverproxy/uring.h new file mode 100644 index 0000000..9c33104 --- /dev/null +++ b/mcserverproxy/uring.h @@ -0,0 +1,73 @@ +#ifndef foouringhfoo +#define foouringhfoo + +extern uint64_t sqe_count; +extern uint64_t cqe_count; + +void uring_task_refdump(struct uring_task *task); + +void uring_task_destroy(struct uring_task *task); + +void uring_task_put(struct uring_task *task); + +void uring_task_get(struct uring_task *task); + +void uring_task_set_buf(struct uring_task *task, struct uring_task_buf *tbuf); + +void uring_task_set_fd(struct uring_task *task, int fd); + +void uring_task_close_fd(struct uring_task *task); + +struct uring_task *uring_parent(); + +void uring_task_init(struct uring_task *task, const char *name, + struct uring_task *parent, + void (*free)(struct uring_task *)); + +void uring_close(struct uring_task *task, int fd); + +void uring_tbuf_write(struct uring_task *task, utask_cb_t final_cb); + +void uring_write(struct uring_task *task, void *buf, size_t len, utask_cb_t cb); + +void uring_tbuf_read_until(struct uring_task *task, + rutask_cb_t is_complete_cb, utask_cb_t final_cb); + +void uring_tbuf_read_until_eof(struct uring_task *task, utask_cb_t final_cb); + +void uring_tbuf_read(struct uring_task *task, utask_cb_t final_cb); + +void uring_read_offset(struct uring_task *task, void *buf, + size_t len, off_t offset, utask_cb_t cb); + +static inline void +uring_read(struct uring_task *task, void *buf, size_t len, utask_cb_t cb) +{ + uring_read_offset(task, buf, len, 0, cb); +} + +void uring_openat(struct uring_task *task, const char *path, utask_cb_t cb); + +void uring_tbuf_recvmsg(struct uring_task *task, utask_cb_t cb); + +void uring_tbuf_sendmsg(struct uring_task *task, utask_cb_t cb); + +void uring_connect(struct uring_task *task, struct saddr *saddr, utask_cb_t cb); + +void uring_accept(struct uring_task *task, struct saddr *saddr, utask_cb_t cb); + +void uring_splice(struct uring_task *task, int fd_in, int fd_out, utask_cb_t cb); + +void uring_poll(struct uring_task *task, short poll_mask, utask_cb_t cb); + +void uring_poll_cancel(struct uring_task *task); + +void uring_delete(); + +void uring_refdump(); + +void uring_init(); + +void uring_event_loop(); + +#endif diff --git a/mcserverproxy/utils.c b/mcserverproxy/utils.c new file mode 100644 index 0000000..eacc586 --- /dev/null +++ b/mcserverproxy/utils.c @@ -0,0 +1,430 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "utils.h" +#include "uring.h" + +static unsigned total_malloc_count = 0; +static int malloc_count = 0; + +LIST_HEAD(malloc_list); + +struct allocation { + const char *allocfn; + const char *callerfn; + int line; + void *ptr; + size_t size; + struct list_head list; +}; + +static void +add_allocation(const char *allocfn, const char *callerfn, int line, void *ptr, size_t size) +{ + struct allocation *a; + + assert_die(!empty_str(allocfn) && !empty_str(callerfn) && line > 0 && ptr && size > 0, "invalid arguments"); + + a = malloc(sizeof(*a)); + if (!a) + die("malloc: %m"); + a->allocfn = allocfn; + a->callerfn = callerfn; + a->line = line; + a->ptr = ptr; + a->size = size; + list_add(&a->list, &malloc_list); + total_malloc_count++; + malloc_count++; + debug(DBG_MALLOC, "called from %s:%i - %s(%zu) = %p (%p)", + callerfn, line, allocfn, size, ptr, a); +} + +void * +__zmalloc(const char *fn, int line, size_t size) +{ + void *ptr; + + assert_die(!empty_str(fn) && line > 0 && size > 0, "invalid arguments"); + + ptr = calloc(1, size); + if (ptr) + add_allocation("zmalloc", fn, line, ptr, size); + return ptr; +} + +char * +__xstrdup(const char *fn, int line, const char *s) +{ + char *ptr; + + assert_die(!empty_str(fn) && line > 0 && !empty_str(s), "invalid arguments"); + + ptr = strdup(s); + if (ptr) + add_allocation("xstrdup", fn, line, ptr, strlen(s) + 1); + return ptr; +} + +char * +__xstrndup(const char *fn, int line, const char *s, size_t n) +{ + char *ptr; + + assert_die(!empty_str(fn) && line > 0 && !empty_str(s) && n > 0, "invalid arguments"); + + ptr = strndup(s, n); + if (ptr) + add_allocation("xstrndup", fn, line, ptr, n); + return ptr; +} + +void +__xfree(const char *fn, int line, void *ptr) +{ + struct allocation *a, *tmp; + unsigned delete_count = 0; + + assert_die(!empty_str(fn) && line > 0, "invalid arguments"); + + if (!ptr) + return; + free(ptr); + malloc_count--; + + debug(DBG_MALLOC, "called from %s:%i - %p", fn, line, ptr); + + list_for_each_entry_safe(a, tmp, &malloc_list, list) { + if (a->ptr == ptr) { + list_del(&a->list); + free(a); + delete_count++; + } + } + + if (delete_count != 1) { + error("Delete count is %u for ptr 0x%p", delete_count, ptr); + exit(EXIT_FAILURE); + } +} + +void +debug_resource_usage() +{ + struct allocation *a; + DIR *dir; + struct dirent *dent; + char buf[4096]; + ssize_t r; + unsigned file_count = 0; + + debug(DBG_MALLOC, "Still malloced %i (total %u)", + malloc_count, total_malloc_count); + + list_for_each_entry(a, &malloc_list, list) { + debug(DBG_MALLOC, "* Lost allocation - %s:%i - ptr: %p, size: %zu", + a->callerfn, a->line, a->ptr, a->size); + } + + dir = opendir("/proc/self/fd"); + if (!dir) { + error("failed to open fd dir"); + return; + } + + debug(DBG_MALLOC, "Open files:"); + while ((dent = readdir(dir)) != NULL) { + if (streq(dent->d_name, ".") || streq(dent->d_name, "..")) + continue; + + r = readlinkat(dirfd(dir), dent->d_name, buf, sizeof(buf)); + if (r < 0) { + debug(DBG_MALLOC, "Failed to readlink %s", dent->d_name); + continue; + } + buf[r] = '\0'; + debug(DBG_MALLOC, " * %s -> %s", dent->d_name, buf); + file_count++; + } + closedir(dir); + + if (file_count > 4) + debug(DBG_MALLOC, "Lost file descriptor(s)"); + + debug(DBG_MALLOC, "CQEs used: %" PRIu64 ", SQEs used: %" PRIu64, + cqe_count, sqe_count); +} + +void +socket_set_low_latency(int sfd) +{ + int option; + + assert_return(sfd >= 0); + + /* Probably not necessary, but can't hurt */ + if (cfg->socket_defer) { + option = true; + if (setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, &option, sizeof(option)) < 0) + error("setsockopt: %m"); + } + + /* Doubtful if it has much effect, but can't hurt */ + if (cfg->socket_iptos) { + option = IPTOS_LOWDELAY; + if (setsockopt(sfd, IPPROTO_IP, IP_TOS, &option, sizeof(option)) < 0) + error("setsockopt: %m"); + } + + /* Nagle's algorithm is a poor fit for gaming */ + if (cfg->socket_nodelay) { + option = true; + if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &option, sizeof(option)) < 0) + error("setsockopt: %m"); + } +} + +void +connection_set_local(struct connection *conn, int fd) +{ + assert_return(conn && fd >= 0); + + conn->local.addrlen = sizeof(conn->local.storage); + if (getsockname(fd, (struct sockaddr *)&conn->local.storage, + &conn->local.addrlen) < 0) + sprintf(conn->local.addrstr, ""); + else + saddr_set_addrstr(&conn->local); +} + +void +connection_set_remote(struct connection *conn, struct saddr *remote) +{ + assert_return(conn && remote); + + conn->remote = *remote; + saddr_set_addrstr(&conn->remote); +} + +static void connect_next(struct uring_task *task, struct connection *conn); + +static void +connect_cb(struct uring_task *task, int res) +{ + struct connection *conn; + + assert_return(task && task->priv); + + conn = task->priv; + if (res < 0) { + debug(DBG_SRV, "%s: connection to %s failed", + task->name, conn->remote.addrstr); + uring_task_close_fd(task); + connect_next(task, conn); + return; + } + + connection_set_local(conn, task->fd); + + debug(DBG_SRV, "%s: connection established %s -> %s", + task->name, conn->local.addrstr, conn->remote.addrstr); + + conn->cb(conn, true); +} + +static void +connect_next(struct uring_task *task, struct connection *conn) +{ + struct saddr *remote, *tmp; + int sfd; + unsigned i; + + assert_return(task && conn && conn->cb); +again: + assert_task_alive_or(DBG_UR, task, goto out); + + i = 0; + remote = NULL; + list_for_each_entry(tmp, conn->addrs, list) { + if (i == conn->next_addr) { + remote = tmp; + break; + } + i++; + } + + if (!remote) { + debug(DBG_SRV, "%s: no more remote addresses to attempt", + task->name); + goto out; + } + + conn->next_addr++; + connection_set_remote(conn, remote); + debug(DBG_SRV, "%s: attempting to connect to %s", + task->name, conn->remote.addrstr); + + sfd = socket(conn->remote.storage.ss_family, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (sfd < 0) { + error("socket: %m"); + goto again; + } + + socket_set_low_latency(sfd); + + task->priv = conn; + uring_task_set_fd(task, sfd); + uring_connect(task, &conn->remote, connect_cb); + return; + +out: + conn->cb(conn, false); +} + +void +connect_any(struct uring_task *task, + struct list_head *addrs, struct connection *conn, + connection_cb_t cb) +{ + assert_return(task && addrs && conn && cb); + + conn->next_addr = 0; + conn->addrs = addrs; + conn->cb = cb; + connect_next(task, conn); +} + +uint16_t +saddr_port(struct saddr *saddr) +{ + assert_return(saddr, 0); + + switch (saddr->storage.ss_family) { + case AF_INET: + return ntohs(saddr->in4.sin_port); + case AF_INET6: + return ntohs(saddr->in6.sin6_port); + default: + return 0; + } +} + +char * +saddr_addr(struct saddr *saddr, char *buf, size_t len) +{ + assert_return(saddr && buf && len > 0, NULL); + + switch (saddr->storage.ss_family) { + case AF_INET: + if (inet_ntop(saddr->in4.sin_family, &saddr->in4.sin_addr, buf, len)) + return buf; + break; + case AF_INET6: + if (inet_ntop(saddr->in6.sin6_family, &saddr->in6.sin6_addr, buf, len)) + return buf; + break; + default: + break; + } + + snprintf(buf, len, ""); + return buf; +} + +void +saddr_set_ipv4(struct saddr *saddr, in_addr_t ip, in_port_t port) +{ + assert_return(saddr); + + memset(&saddr->in4, 0, sizeof(saddr->in4)); + saddr->in4.sin_family = AF_INET; + saddr->in4.sin_port = port; + saddr->in4.sin_addr.s_addr = ip; + saddr->addrlen = sizeof(saddr->in4); + saddr_set_addrstr(saddr); +} + +void +saddr_set_ipv6(struct saddr *saddr, const struct in6_addr *ip, in_port_t port) +{ + assert_return(saddr && ip); + + memset(&saddr->in6, 0, sizeof(saddr->in6)); + saddr->in6.sin6_family = AF_INET6; + saddr->in6.sin6_port = port; + if (ip) + saddr->in6.sin6_addr = *ip; + saddr->addrlen = sizeof(saddr->in6); + saddr_set_addrstr(saddr); +} + +void +saddr_set_addrstr(struct saddr *saddr) +{ + assert_return(saddr); + + char abuf[ADDRSTRLEN]; + + switch (saddr->storage.ss_family) { + case AF_INET: + snprintf(saddr->addrstr, sizeof(saddr->addrstr), + "AF_INET4 %s %" PRIu16, + saddr_addr(saddr, abuf, sizeof(abuf)), + saddr_port(saddr)); + break; + case AF_INET6: + snprintf(saddr->addrstr, sizeof(saddr->addrstr), + "AF_INET6 %s %" PRIu16, + saddr_addr(saddr, abuf, sizeof(abuf)), + saddr_port(saddr)); + break; + default: + snprintf(saddr->addrstr, sizeof(saddr->addrstr), "AF_UNKNOWN"); + break; + } +} + +int +strtou16_strict(const char *str, uint16_t *result) +{ + char *end; + long val; + + assert_return(!empty_str(str) && result, -EINVAL); + + errno = 0; + val = strtol(str, &end, 10); + + if (errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) + return -EINVAL; + + if (errno != 0 && val == 0) + return -EINVAL; + + if (end == str) + return -EINVAL; + + if (*end != '\0') + return -EINVAL; + + if (val < 1 || val > UINT16_MAX) + return -EINVAL; + + if (result) + *result = val; + return 0; +} + diff --git a/mcserverproxy/utils.h b/mcserverproxy/utils.h new file mode 100644 index 0000000..c36a36c --- /dev/null +++ b/mcserverproxy/utils.h @@ -0,0 +1,245 @@ +#ifndef fooutilshfoo +#define fooutilshfoo + +#include +#include +#include +#include +#include + +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +#define zmalloc(s) __zmalloc(__func__, __LINE__, s) +void *__zmalloc(const char *fn, int line, size_t s); + +#define xstrdup(s) __xstrdup(__func__, __LINE__, s) +char *__xstrdup(const char *fn, int line, const char *s); + +#define xstrndup(s, n) __xstrndup(__func__, __LINE__, s, n) +char *__xstrndup(const char *fn, int line, const char *s, size_t n); + +#define xfree(s) __xfree(__func__, __LINE__, s) +void __xfree(const char *fn, int line, void *ptr); + +void debug_resource_usage(); + +/* Length of longest DNS name = 253 + trailing dot */ +#define FQDN_STR_LEN 254 + +/* Length of longest port string = strlen("65535") */ +#define PORT_STR_LEN 5 + +/* Length of longest address family string = strlen("AF_INETX") */ +#define AF_STR_LEN 8 + +/* Length of longest addrstr, format = "AF_INETX */ +#define ADDRSTRLEN (AF_STR_LEN + 1 + INET6_ADDRSTRLEN + 1 + PORT_STR_LEN + 1) +struct saddr { + union { + struct sockaddr_storage storage; + struct sockaddr_in in4; + struct sockaddr_in6 in6; + struct sockaddr_ll ll; + }; + socklen_t addrlen; + char addrstr[ADDRSTRLEN]; + struct list_head list; +}; + +struct connection; + +typedef void(*connection_cb_t)(struct connection *, bool); + +struct connection { + struct saddr remote; + struct saddr local; + + struct list_head *addrs; + unsigned next_addr; + + connection_cb_t cb; +}; + +struct uring_task; + +void socket_set_low_latency(int sfd); + +void connection_set_local(struct connection *conn, int fd); + +void connection_set_remote(struct connection *conn, struct saddr *remote); + +void connect_any(struct uring_task *task, + struct list_head *addrs, struct connection *conn, + connection_cb_t cb); + +char *saddr_addr(struct saddr *saddr, char *buf, size_t len); + +uint16_t saddr_port(struct saddr *saddr); + +void saddr_set_ipv4(struct saddr *saddr, in_addr_t ip, in_port_t port); + +void saddr_set_ipv6(struct saddr *saddr, const struct in6_addr *ip, in_port_t port); + +void saddr_set_addrstr(struct saddr *saddr); + +int strtou16_strict(const char *str, uint16_t *result); + +static inline bool empty_str(const char *str) +{ + if (!str || str[0] == '\0') + return true; + else + return false; +} + +static inline bool streq(const char *a, const char *b) +{ + return strcmp(a, b) == 0; +} + +static inline bool strcaseeq(const char *a, const char *b) +{ + return strcasecmp(a, b) == 0; +} + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define chtobe32(x) __bswap_constant_32(x) +#else +#define chtobe32(x) (x) +#endif + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define cinet_addr(a,b,c,d) ((uint32_t)((a)<<0|(b)<<8|(c)<<16|(d)<<24)) +#else +#define cinet_addr(a,b,c,d) ((uint32_t)((a)<<24|(b)<<16|(c)<<8|(d)<<0)) +#endif + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) + +static inline void list_init(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +static inline void list_del(struct list_head *list) +{ + list->next->prev = list->prev; + list->prev->next = list->next; +} + +static inline void list_add(struct list_head *new, struct list_head *list) +{ + list->next->prev = new; + new->next = list->next; + new->prev = list; + list->next = new; +} + +static inline void list_replace(struct list_head *old, struct list_head *new) +{ + old->prev->next = new; + old->next->prev = new; + new->next = old->next; + new->prev = old->prev; + old->next = old->prev = NULL; +} + +static inline bool list_empty(struct list_head *list) +{ + return list->next == list; +} + +#define PIPE_RD 0 +#define PIPE_WR 1 + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) + +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +#define list_next_entry(pos, member) \ + list_entry((pos)->member.next, typeof(*(pos)), member) + +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +#define list_for_each_entry(pos, head, member) \ + for (pos = list_first_entry(head, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_next_entry(pos, member)) + +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/* +#define _cleanup_(x) __attribute__((cleanup(x))) + +#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ + static inline void func##p(type *p) { \ + if (*p) \ + func(*p); \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +static inline unsigned +strv_length(char **strv) +{ + unsigned len; + + for (len = 0; strv && *strv; strv++) + len++; + + return len; +} + +static inline void strv_free(char **l) { + char **k; + if (l) { + for (k = l; *k; k++) + free(k); + free(l); + } +} +DEFINE_TRIVIAL_CLEANUP_FUNC(char **, strv_free); +#define _cleanup_strv_free_ _cleanup_(strv_freep) + +static inline void freep(void *p) { + free(*(void**) p); +} +#define _cleanup_free_ _cleanup_(freep) + +DEFINE_TRIVIAL_CLEANUP_FUNC(int, close); +#define _cleanup_close_ _cleanup_(closep) + +DEFINE_TRIVIAL_CLEANUP_FUNC(FILE *, fclose); +#define _cleanup_fclose_ _cleanup_(fclosep) + +DEFINE_TRIVIAL_CLEANUP_FUNC(DIR *, closedir); +#define _cleanup_closedir_ _cleanup_(closedirp) + +*/ + +#endif + diff --git a/meson.build b/meson.build index eb446fe..1f8424a 100644 --- a/meson.build +++ b/meson.build @@ -13,7 +13,8 @@ conf.set_quoted('VERSION', '@0@-@VCS_TAG@'.format(meson.project_version())) conf.set_quoted('DEFAULT_CFG_DIR', sysconfdir) conf.set_quoted('DEFAULT_MAIN_CFG_FILE', mainconfname) -config_h = declare_dependency( +inc_config_h = include_directories('.') +dep_config_h = declare_dependency( sources: vcs_tag( command: ['git', 'rev-parse', '--short', 'HEAD'], fallback: get_option('profile') != 'default' ? 'devel' : 'stable', @@ -23,52 +24,11 @@ config_h = declare_dependency( configuration: conf ), output: 'config.h' - ) + ), + include_directories : inc_config_h, ) -configuration_inc = include_directories('.') - -liburing = dependency('liburing') -libsystemd = dependency('libsystemd') -libcapng = dependency('libcap-ng') - -mcproxy_sources = [ - 'main.c', - 'uring.c', - 'signal-handler.c', - 'server.c', - 'server-proxy.c', - 'server-config.c', - 'server-rcon.c', - 'rcon-protocol.c', - 'announce.c', - 'config-parser.c', - 'idle.c', - 'ptimer.c', - 'igmp.c', - 'systemd.c', - 'utils.c' -] - -mcserverctl_sources = [ - 'mcserverctl.c', - 'rcon-protocol.c', -] - -executable('mcproxy', - mcproxy_sources, - link_args: [ '-lanl' ], - include_directories : configuration_inc, - dependencies: [ - liburing, - libsystemd, - libcapng, - config_h - ], -) - -executable('mcserverctl', - mcserverctl_sources, - include_directories : configuration_inc, -) +subdir('shared') +subdir('mcserverproxy') +subdir('mcserverctl') diff --git a/ptimer.c b/ptimer.c deleted file mode 100644 index 5f9cf5d..0000000 --- a/ptimer.c +++ /dev/null @@ -1,223 +0,0 @@ -#include -#include -#include -#include -#include - -#include "main.h" -#include "uring.h" -#include "ptimer.h" - -struct ptimer { - uint64_t value; - time_t previous_time; - struct uring_task task; - unsigned task_count; - struct list_head ptasks; -}; - -static void -ptimer_set(unsigned value, unsigned interval) -{ - struct itimerspec tspec = { - .it_interval = { - .tv_sec = interval, - .tv_nsec = 0 - }, - .it_value = { - .tv_sec = value, - .tv_nsec = 0 - } - }; - - assert_return(cfg->ptimer && cfg->ptimer->task.fd >= 0); - - if (timerfd_settime(cfg->ptimer->task.fd, 0, &tspec, NULL) != 0) - error("timerfd_settime: %m"); -} - -static unsigned -gcd(unsigned a, unsigned b) -{ - if (a == 0) - return b; - return gcd(b % a, a); -} - -static unsigned -array_gcd(unsigned arr[], unsigned n) -{ - unsigned result = arr[0]; - - for (unsigned i = 1; i < n; i++) - result = gcd(arr[i], result); - - return result; -} - -static void -ptimer_tick(struct ptimer *ptimer) -{ - time_t now = time(NULL); - unsigned diff = (unsigned)(now - ptimer->previous_time); - struct ptimer_task *ptask, *ptmp; - - debug(DBG_TIMER, "got a tick of %u secs", diff); - list_for_each_entry_safe(ptask, ptmp, &ptimer->ptasks, list) { - if (ptask->remain > diff) { - ptask->remain -= diff; - continue; - } - - debug(DBG_TIMER, "triggering ptask %p (times %u)", - ptask, ptask->times); - - ptask->cb(ptask); - ptask->remain = ptask->interval; - - if (ptask->times == 0) - continue; - - ptask->times--; - if (ptask->times == 0) { - ptask->active = false; - list_del(&ptask->list); - } - } - - ptimer->previous_time = now; -} - -static void -ptimer_reconfig(struct ptimer *ptimer) -{ - struct ptimer_task *ptask; - unsigned i = 0; - unsigned lowest = ~0; - unsigned interval; - - if (list_empty(&ptimer->ptasks)) { - debug(DBG_TIMER, "no tasks"); - ptimer_set(0, 0); - return; - } - - unsigned intervals[ptimer->task_count]; - - list_for_each_entry(ptask, &ptimer->ptasks, list) { - if (ptask->remain < lowest) - lowest = ptask->remain; - intervals[i++] = ptask->interval; - } - - interval = array_gcd(intervals, i); - - debug(DBG_TIMER, "lowest: %u, gcd: %u\n", lowest, interval); - ptimer_set(lowest, interval); -} - -void -ptimer_del_task(struct ptimer_task *ptask) -{ - struct ptimer *ptimer = cfg->ptimer; - - assert_return(ptask && ptimer); - assert_return_silent(ptask->active); - assert_return(ptimer->task_count > 0); - - list_del(&ptask->list); - ptask->active = false; - ptimer->task_count--; - ptimer_tick(ptimer); - ptimer_reconfig(ptimer); - uring_task_put(&ptimer->task); -} - -void -ptimer_add_task(struct ptimer_task *ptask) -{ - struct ptimer *ptimer = cfg->ptimer; - - assert_return(ptask && ptask->interval > 0 && ptask->cb && ptimer); - assert_return_silent(!ptask->active); - - uring_task_get(&ptimer->task); - ptask->active = true; - ptask->remain = ptask->interval; - ptimer_tick(ptimer); - list_add(&ptask->list, &ptimer->ptasks); - ptimer->task_count++; - ptimer_reconfig(ptimer); -} - -void -ptimer_refdump() -{ - assert_return(cfg->ptimer); - - uring_task_refdump(&cfg->ptimer->task); -} - -static void -ptimer_free(struct uring_task *task) -{ - struct ptimer *ptimer = container_of(task, struct ptimer, task); - - assert_return(task); - - debug(DBG_TIMER, "task %p, ptimer %p", task, ptimer); - xfree(ptimer); - cfg->ptimer = NULL; -} - -void -ptimer_delete() -{ - assert_return(cfg->ptimer); - - debug(DBG_TIMER, "closing fd %i", cfg->ptimer->task.fd); - uring_task_destroy(&cfg->ptimer->task); -} - -static void -ptimer_cb(struct uring_task *task, int res) -{ - struct ptimer *ptimer = container_of(task, struct ptimer, task); - - assert_return(task); - assert_task_alive(DBG_IGMP, task); - - if (res != sizeof(ptimer->value)) { - error("timerfd_read: res: %i, %m", res); - return; - } - - ptimer_tick(ptimer); - uring_read(&ptimer->task, &ptimer->value, sizeof(ptimer->value), ptimer_cb); -} - -void -ptimer_init() -{ - struct ptimer *ptimer; - int tfd; - - assert_return(!cfg->ptimer); - - ptimer = zmalloc(sizeof(*ptimer)); - if (!ptimer) - die("malloc: %m"); - - tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); - if (tfd < 0) - die("timerfd_create: %m"); - - ptimer->task_count = 0; - ptimer->previous_time = time(NULL); - list_init(&ptimer->ptasks); - uring_task_init(&ptimer->task, "ptimer", uring_parent(), ptimer_free); - uring_task_set_fd(&ptimer->task, tfd); - cfg->ptimer = ptimer; - uring_read(&ptimer->task, &ptimer->value, sizeof(ptimer->value), ptimer_cb); -} - diff --git a/ptimer.h b/ptimer.h deleted file mode 100644 index 0b53590..0000000 --- a/ptimer.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef fooptimerhfoo -#define fooptimerhfoo - -struct ptimer_task { - unsigned interval; - unsigned times; - void (*cb)(struct ptimer_task *); - bool active; - unsigned remain; - struct list_head list; -}; - -static inline void -ptask_init(struct ptimer_task *ptask, unsigned interval, - unsigned times, void(*cb)(struct ptimer_task *)) -{ - ptask->interval = interval; - ptask->times = times; - ptask->cb = cb; - ptask->active = false; -} - -void ptimer_del_task(struct ptimer_task *ptask); - -void ptimer_add_task(struct ptimer_task *ptask); - -void ptimer_refdump(); - -void ptimer_delete(); - -void ptimer_init(); - -#endif diff --git a/rcon-protocol.c b/rcon-protocol.c deleted file mode 100644 index 0ea1245..0000000 --- a/rcon-protocol.c +++ /dev/null @@ -1,185 +0,0 @@ -#include -#include -#include -#include - -#include "rcon-protocol.h" - -static int32_t -read_int(const char **pos, size_t *len) -{ - uint32_t val; - const char *p; - - if (!pos || !*pos) - return 0; - - if (len && *len < RCON_INT_LEN) - return 0; - - p = *pos; - val = ((uint8_t)p[0] << 0); - val += ((uint8_t)p[1] << 8); - val += ((uint8_t)p[2] << 16); - val += ((uint8_t)p[3] << 24); - - *pos += RCON_INT_LEN; - if (len) - *len -= RCON_INT_LEN; - - return (int32_t)val; -} - -static void -write_int(char **pos, size_t *len, int32_t orig) -{ - uint32_t val = (uint32_t)orig; - char *p; - - if (!pos || !*pos) - return; - - p = *pos; - p[0] = (val >> 0) & 0xff; - p[1] = (val >> 8) & 0xff; - p[2] = (val >> 16) & 0xff; - p[3] = (val >> 24) & 0xff; - - *pos += RCON_INT_LEN; - if (len) - *len += RCON_INT_LEN; -} - -static void -write_str(char **pos, size_t *len, const char *str, size_t slen) -{ - if (!pos || !*pos || !str || *str == '\0' || slen < 1) - return; - - memcpy(*pos, str, slen); - *pos += slen; - if (len) - *len += slen; -} - -static void -write_end(char **pos, size_t *len) -{ - char *p; - - if (!pos || !*pos) - return; - - p = *pos; - p[0] = 0x00; - p[1] = 0x00; - - *pos += RCON_END_LEN; - if (len) - *len += RCON_END_LEN; -} - -bool -rcon_protocol_create_packet(char *buf, size_t len, size_t *rlen, int32_t reqid, - enum rcon_packet_type type, const char *msg) -{ - size_t plen, msglen; - char *pos; - - if (!buf || !rlen || !msg || msg[0] == '\0') - return false; - - msglen = strlen(msg); - if (len < rcon_protocol_packet_len(msglen)) - return false; - - /* Body */ - pos = buf + RCON_HDR_LEN; - plen = RCON_HDR_LEN; - write_int(&pos, &plen, reqid); - write_int(&pos, &plen, type); - write_str(&pos, &plen, msg, msglen); - write_end(&pos, &plen); - - /* Header (length of body) */ - pos = buf; - write_int(&pos, NULL, plen - RCON_HDR_LEN); - - *rlen = plen; - return true; -} - -/* - * Returns: - * < 0 = error - * 0 = packet not complete - * > 0 = packet complete (length) - */ -int32_t -rcon_protocol_packet_complete(const char *buf, size_t len) -{ - const char *pos; - int32_t plen; - - if (!buf) - return -1; - - if (len < RCON_PKT_MIN_LEN) - return 0; - - pos = buf; - plen = read_int(&pos, NULL) + RCON_HDR_LEN; - - if (plen < RCON_HDR_LEN) - /* Read a negative int */ - return -1; - - if (len < plen) - return 0; - else - return plen; -} - -bool -rcon_protocol_read_packet(const char *buf, size_t len, int32_t *id, int32_t *type, - const char **rmsg, const char **error) -{ - const char *pos; - int32_t plen; - - if (!error) - return false; - - if (!buf || !id || !type || !rmsg) { - *error = "invalid arguments"; - return false; - } - - plen = rcon_protocol_packet_complete(buf, len); - *error = "invalid packet length"; - if (plen < RCON_PKT_MIN_LEN) - return false; - - pos = buf + RCON_HDR_LEN; - len -= RCON_HDR_LEN; - - *id = read_int(&pos, &len); - *type = read_int(&pos, &len); - *rmsg = NULL; - - if (len < RCON_END_LEN) - return false; - else if (len > RCON_END_LEN) { - *rmsg = pos; - pos += len - RCON_END_LEN; - len = RCON_END_LEN; - } - - if (pos[0] != '\0' || pos[1] != '\0') { - *error = "invalid trailer"; - return false; - } - - *error = NULL; - return true; -} diff --git a/rcon-protocol.h b/rcon-protocol.h deleted file mode 100644 index 35997c4..0000000 --- a/rcon-protocol.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef foorconprotocolhfoo -#define foorconprotocolhfoo - -#include -#include - -enum rcon_packet_type { - RCON_PACKET_LOGIN = 3, - RCON_PACKET_LOGIN_OK = 2, - RCON_PACKET_LOGIN_FAIL = -1, - RCON_PACKET_COMMAND = 2, - RCON_PACKET_RESPONSE = 0, -}; - -#define RCON_INT_LEN 4 - -#define RCON_END_LEN 2 - -#define RCON_HDR_LEN 4 - -/* header + reqid + type + end */ -#define RCON_PKT_MIN_LEN (RCON_HDR_LEN + 2 * RCON_INT_LEN + RCON_END_LEN) - -static inline size_t -rcon_protocol_packet_len(size_t msglen) -{ - /* header + reqid + type + msg + end */ - return (RCON_PKT_MIN_LEN + msglen); -} - -bool rcon_protocol_create_packet(char *buf, size_t len, size_t *rlen, - int32_t reqid, enum rcon_packet_type type, - const char *msg); - -int32_t rcon_protocol_packet_complete(const char *buf, size_t len); - -bool rcon_protocol_read_packet(const char *buf, size_t len, int32_t *id, - int32_t *type, const char **rmsg, - const char **error); - -#endif diff --git a/server-config.c b/server-config.c deleted file mode 100644 index 549cf16..0000000 --- a/server-config.c +++ /dev/null @@ -1,580 +0,0 @@ -#define _GNU_SOURCE -#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" - -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); -} - -enum scfg_keys { - SCFG_KEY_INVALID = 0, - SCFG_KEY_TYPE, - SCFG_KEY_NAME, - SCFG_KEY_PORT, - SCFG_KEY_LOCAL, - SCFG_KEY_REMOTE, - SCFG_KEY_IDLE_TIMEOUT, - SCFG_KEY_STOP_METHOD, - SCFG_KEY_START_METHOD, - SCFG_KEY_STOP_EXEC, - SCFG_KEY_START_EXEC, - SCFG_KEY_RCON, - SCFG_KEY_RCON_PASSWORD, - SCFG_KEY_SYSTEMD_SERVICE, -}; - -struct cfg_key_value_map scfg_key_map[] = { - { - .key_name = "type", - .key_value = SCFG_KEY_TYPE, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "name", - .key_value = SCFG_KEY_NAME, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "port", - .key_value = SCFG_KEY_PORT, - .value_type = CFG_VAL_TYPE_UINT16, - }, { - .key_name = "local", - .key_value = SCFG_KEY_LOCAL, - .value_type = CFG_VAL_TYPE_ASYNC_ADDRS, - }, { - .key_name = "remote", - .key_value = SCFG_KEY_REMOTE, - .value_type = CFG_VAL_TYPE_ASYNC_ADDRS, - }, { - .key_name = "idle_timeout", - .key_value = SCFG_KEY_IDLE_TIMEOUT, - .value_type = CFG_VAL_TYPE_UINT16, - }, { - .key_name = "stop_method", - .key_value = SCFG_KEY_STOP_METHOD, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "start_method", - .key_value = SCFG_KEY_START_METHOD, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "stop_exec", - .key_value = SCFG_KEY_STOP_EXEC, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "start_exec", - .key_value = SCFG_KEY_START_EXEC, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "rcon", - .key_value = SCFG_KEY_RCON, - .value_type = CFG_VAL_TYPE_ASYNC_ADDRS, - }, { - .key_name = "rcon_password", - .key_value = SCFG_KEY_RCON_PASSWORD, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "systemd_service", - .key_value = SCFG_KEY_SYSTEMD_SERVICE, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = NULL, - .key_value = SCFG_KEY_INVALID, - .value_type = CFG_VAL_TYPE_INVALID, - } -}; - -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->name, "server", &pos)) - 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)) - 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); -} - -static bool -scfg_valid_filename(const char *name) -{ - const char *suffix; - - if (empty_str(name)) - return false; - if (name[0] == '.') - return false; - if ((suffix = strrchr(name, '.')) == NULL) - return false; - if (!streq(suffix, ".server")) - return false; - - return true; -} - -struct server_cfg_monitor { - struct uring_task task; - char buf[4096] __attribute__((aligned(__alignof__(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 (!scfg_valid_filename(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 (dent->d_type != DT_REG && dent->d_type != DT_UNKNOWN) - continue; - if (!scfg_valid_filename(dent->d_name)) - continue; - - server = server_new(dent->d_name); - if (server) - uring_openat(&server->task, server->name, scfg_open_cb); - } - - closedir(dir); -} - diff --git a/server-config.h b/server-config.h deleted file mode 100644 index 590dae0..0000000 --- a/server-config.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef fooserverconfighfoo -#define fooserverconfighfoo - -void server_cfg_monitor_delete(); - -void server_cfg_monitor_refdump(); - -void server_cfg_monitor_init(); - -#endif diff --git a/server-proxy.c b/server-proxy.c deleted file mode 100644 index 4cbbb87..0000000 --- a/server-proxy.c +++ /dev/null @@ -1,578 +0,0 @@ -#define _GNU_SOURCE -#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 "utils.h" - -static void -format_bytes(char *buf, size_t len, uint64_t val) -{ - uint64_t tmp; - const char *suffix = "B"; - - assert_return(buf && len > 0); - - tmp = val * 10; - if (val > 1152921504606846976ULL) { - tmp = val / 115292150460684697ULL; - suffix= "EiB"; - } else if (val > 1125899906842624ULL) { - tmp /= 1125899906842624ULL; - suffix = "PiB"; - } else if (val > 1099511627776ULL) { - tmp /= 1099511627776ULL; - suffix = "TiB"; - } else if (val > 1073741824ULL) { - tmp /= 1073741824ULL; - suffix = "GiB"; - } else if (val > 1048576) { - tmp /= 1048576; - suffix = "MiB"; - } else if (val > 1024) { - tmp /= 1024; - suffix = "KiB"; - } - - snprintf(buf, len, "%lu.%lu %s", tmp / 10, tmp % 10, suffix); -} - -static void -format_time(char *buf, size_t len, time_t diff) -{ - unsigned hh, mm, ss; - - assert_return(buf && len > 0); - - hh = diff / 3600; - diff %= 3600; - mm = diff / 60; - diff %= 60; - ss = diff; - - snprintf(buf, len, "%02u:%02u:%02u", hh, mm, ss); -} - -static void -proxy_free(struct uring_task *task) -{ - struct server_proxy *proxy = container_of(task, struct server_proxy, task); - char cts[100]; - char stc[100]; - char duration[100]; - - assert_return(task); - - debug(DBG_PROXY, "server: %s, src: %s, dst: %s", - proxy->server->name, - proxy->client_conn.remote.addrstr, - proxy->server_conn.remote.addrstr); - - if (proxy->begin > 0) { - format_time(duration, sizeof(duration), time(NULL) - proxy->begin); - format_bytes(cts, sizeof(cts), proxy->client_bytes); - format_bytes(stc, sizeof(stc), proxy->server_bytes); - - info("%s: proxy connection %s -> %s closed " - "(CtS: %s, StC: %s), duration %s", - proxy->server->name, - proxy->client_conn.remote.addrstr, - proxy->server_conn.remote.addrstr, - cts, stc, duration); - } - - list_del(&proxy->list); - xfree(proxy); -} - -static void -proxy_client_free(struct uring_task *task) -{ - struct server_proxy *proxy = container_of(task, struct server_proxy, clienttask); - - assert_return(task); - - debug(DBG_PROXY, "%s: client connection closed", proxy->server->name); -} - -static void -proxy_server_free(struct uring_task *task) -{ - struct server_proxy *proxy = container_of(task, struct server_proxy, servertask); - - assert_return(task); - - debug(DBG_PROXY, "%s: server connection closed", proxy->server->name); -} - -void -proxy_delete(struct server_proxy *proxy) -{ - debug(DBG_PROXY, "%s: shutting down proxy %p", proxy->server->name, proxy); - - assert_return(proxy); - - ptimer_del_task(&proxy->ptask); - - if (cfg->splice_supported) { - uring_close(&proxy->server->task, proxy->cpipe[PIPE_RD]); - uring_close(&proxy->server->task, proxy->cpipe[PIPE_WR]); - uring_close(&proxy->server->task, proxy->spipe[PIPE_RD]); - uring_close(&proxy->server->task, proxy->spipe[PIPE_WR]); - } - - uring_task_set_fd(&proxy->servertask, proxy->sfd); - uring_task_destroy(&proxy->servertask); - uring_task_set_fd(&proxy->clienttask, proxy->cfd); - uring_task_destroy(&proxy->clienttask); - uring_task_destroy(&proxy->task); -} - -/* - * These four functions provide the fallback read-write mode - */ -static void proxy_client_read(struct uring_task *task, int res); - -static void -proxy_client_written(struct uring_task *task, int res) -{ - struct server_proxy *proxy = container_of(task, struct server_proxy, clienttask); - - assert_return(task); - assert_task_alive(DBG_PROXY, task); - - if (res <= 0) { - debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); - proxy_delete(proxy); - return; - } - - proxy->client_bytes += res; - uring_task_set_fd(&proxy->clienttask, proxy->cfd); - uring_tbuf_read(task, proxy_client_read); -} - -static void -proxy_client_read(struct uring_task *task, int res) -{ - struct server_proxy *proxy = container_of(task, struct server_proxy, clienttask); - - assert_return(task); - assert_task_alive(DBG_PROXY, task); - - if (res <= 0) { - debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); - proxy_delete(proxy); - return; - } - - uring_task_set_fd(&proxy->clienttask, proxy->sfd); - uring_tbuf_write(task, proxy_client_written); -} - -static void proxy_server_read(struct uring_task *task, int res); - -static void -proxy_server_written(struct uring_task *task, int res) -{ - struct server_proxy *proxy = container_of(task, struct server_proxy, servertask); - - assert_return(task); - assert_task_alive(DBG_PROXY, task); - - if (res <= 0) { - debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); - proxy_delete(proxy); - return; - } - - proxy->server_bytes += res; - uring_task_set_fd(&proxy->servertask, proxy->sfd); - uring_tbuf_read(&proxy->servertask, proxy_server_read); -} - -static void -proxy_server_read(struct uring_task *task, int res) -{ - struct server_proxy *proxy = container_of(task, struct server_proxy, servertask); - - assert_return(task); - assert_task_alive(DBG_PROXY, task); - - if (res <= 0) { - debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); - proxy_delete(proxy); - return; - } - - uring_task_set_fd(&proxy->servertask, proxy->cfd); - uring_tbuf_write(task, proxy_server_written); -} - -/* - * These four functions provide the splice fd->pipe->fd mode - */ -static void proxy_client_spliced_in(struct uring_task *task, int res); - -static void -proxy_client_spliced_out(struct uring_task *task, int res) -{ - struct server_proxy *proxy = container_of(task, struct server_proxy, servertask); - - assert_return(task); - assert_task_alive(DBG_PROXY, task); - - if (res <= 0) { - debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); - proxy_delete(proxy); - return; - } - - uring_splice(task, proxy->cfd, proxy->cpipe[PIPE_WR], proxy_client_spliced_in); -} - -static void -proxy_client_spliced_in(struct uring_task *task, int res) -{ - struct server_proxy *proxy = container_of(task, struct server_proxy, servertask); - - assert_return(task); - assert_task_alive(DBG_PROXY, task); - - if (res <= 0) { - debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); - proxy_delete(proxy); - return; - } - - uring_splice(task, proxy->cpipe[PIPE_RD], proxy->sfd, proxy_client_spliced_out); -} - -static void proxy_server_spliced_in(struct uring_task *task, int res); - -static void -proxy_server_spliced_out(struct uring_task *task, int res) -{ - struct server_proxy *proxy = container_of(task, struct server_proxy, servertask); - - assert_return(task); - assert_task_alive(DBG_PROXY, task); - - if (res <= 0) { - debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); - proxy_delete(proxy); - return; - } - - uring_splice(task, proxy->sfd, proxy->spipe[PIPE_WR], proxy_server_spliced_in); -} - -static void -proxy_server_spliced_in(struct uring_task *task, int res) -{ - struct server_proxy *proxy = container_of(task, struct server_proxy, servertask); - - assert_return(task); - assert_task_alive(DBG_PROXY, task); - - if (res <= 0) { - debug(DBG_PROXY, "%s: res: %i", proxy->server->name, res); - proxy_delete(proxy); - return; - } - - uring_splice(task, proxy->spipe[PIPE_RD], proxy->cfd, proxy_server_spliced_out); -} - -static void -proxy_connected_cb(struct connection *conn, bool connected) -{ - struct server_proxy *proxy = container_of(conn, struct server_proxy, server_conn); - - assert_return(conn); - assert_task_alive(DBG_PROXY, &proxy->clienttask); - assert_task_alive(DBG_PROXY, &proxy->servertask); - - proxy->connecting = false; - if (!connected) { - error("%s: proxy connection to remote server failed", - proxy->server->name); - if (!proxy->ptask.active) - proxy_delete(proxy); - return; - } - - ptimer_del_task(&proxy->ptask); - - proxy->sfd = proxy->servertask.fd; - verbose("%s: proxy connection %s -> %s opened", - proxy->server->name, - proxy->client_conn.remote.addrstr, - proxy->server_conn.remote.addrstr); - proxy->begin = time(NULL); - - if (cfg->splice_supported) { - debug(DBG_PROXY, "handling proxy connection with splice"); - uring_splice(&proxy->clienttask, proxy->cfd, proxy->cpipe[PIPE_WR], proxy_client_spliced_in); - uring_splice(&proxy->servertask, proxy->sfd, proxy->spipe[PIPE_WR], proxy_server_spliced_in); - } else { - debug(DBG_PROXY, "handling proxy connection with read-write"); - uring_tbuf_read(&proxy->clienttask, proxy_client_read); - uring_tbuf_read(&proxy->servertask, proxy_server_read); - } -} - -void -proxy_refdump(struct server_proxy *proxy) -{ - assert_return(proxy); - - uring_task_refdump(&proxy->task); - uring_task_refdump(&proxy->clienttask); - uring_task_refdump(&proxy->servertask); -} - -static void -proxy_connect_timer_cb(struct ptimer_task *ptask) -{ - struct server_proxy *proxy = container_of(ptask, struct server_proxy, ptask); - - assert_return(ptask); - - if (proxy->connecting) - return; - - proxy->connecting = true; - connect_any(&proxy->servertask, &proxy->server->remotes, - &proxy->server_conn, proxy_connected_cb); -} - -struct server_proxy * -proxy_new(struct server *server, struct saddr *client, int fd) -{ - struct server_proxy *proxy; - - assert_return(server && client && fd > 0, NULL); - - proxy = zmalloc(sizeof(*proxy)); - if (!proxy) { - error("malloc: %m"); - goto out; - } - - if (cfg->splice_supported) { - if (pipe2(proxy->cpipe, O_CLOEXEC) < 0) { - error("pipe2: %m"); - goto out_free; - } - - if (pipe2(proxy->spipe, O_CLOEXEC) < 0) { - error("pipe2: %m"); - goto out_close_cpipe; - } - } - - proxy->sfd = -1; - proxy->cfd = fd; - proxy->server = server; - uring_task_init(&proxy->task, "proxy", &server->task, proxy_free); - - connection_set_local(&proxy->client_conn, fd); - connection_set_remote(&proxy->client_conn, client); - - uring_task_init(&proxy->clienttask, "proxy_client", &proxy->task, - proxy_client_free); - uring_task_set_buf(&proxy->clienttask, &proxy->clientbuf); - uring_task_set_fd(&proxy->clienttask, fd); - - uring_task_init(&proxy->servertask, "proxy_server", &proxy->task, - proxy_server_free); - uring_task_set_buf(&proxy->servertask, &proxy->serverbuf); - - list_add(&proxy->list, &server->proxys); - - if (server->state != SERVER_STATE_RUNNING) { - if (server_start(server) && - cfg->proxy_connection_interval > 0 && - cfg->proxy_connection_attempts > 0) { - ptask_init(&proxy->ptask, - cfg->proxy_connection_interval, - cfg->proxy_connection_attempts, - proxy_connect_timer_cb); - ptimer_add_task(&proxy->ptask); - } - } - - proxy->connecting = true; - connect_any(&proxy->servertask, &server->remotes, - &proxy->server_conn, proxy_connected_cb); - - return proxy; - -out_close_cpipe: - uring_close(&server->task, proxy->cpipe[PIPE_RD]); - uring_close(&server->task, proxy->cpipe[PIPE_WR]); -out_free: - xfree(proxy); -out: - return NULL; -} - -static void -local_accept(struct uring_task *task, int res) -{ - struct server_local *local = container_of(task, struct server_local, task); - struct server *server = container_of(task->parent, struct server, task); - struct server_proxy *proxy; - - assert_return(task); - assert_task_alive(DBG_PROXY, task); - - debug(DBG_PROXY, "task %p, res %i, server %s", task, res, server->name); - - if (res < 0) { - error("res: %i", res); - goto out; - } - - saddr_set_addrstr(&local->client); - - verbose("%s: incoming proxy connection: %s -> %s", - server->name, local->client.addrstr, local->local.addrstr); - - if (list_empty(&server->remotes)) { - /* This shouldn't be possible, checked before opening local */ - error("server->remotes empty!"); - uring_close(&local->task, res); - goto out; - } - - proxy = proxy_new(server, &local->client, res); - if (!proxy) - uring_close(&local->task, res); - -out: - uring_accept(&local->task, &local->client, local_accept); -} - -bool -local_open(struct server_local *local) -{ - int sfd; - int option; - int r; - - assert_return(local && local->server, false); - - sfd = socket(local->local.storage.ss_family, SOCK_STREAM | SOCK_CLOEXEC, 0); - if (sfd < 0) { - error("socket: %m"); - goto error; - } - - option = true; - if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)) < 0) { - error("setsockopt: %m"); - goto error; - } - - /* The MC protocol expects the client to send data first */ - if (cfg->socket_defer) { - option = true; - if (setsockopt(sfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &option, sizeof(option)) < 0) - error("setsockopt: %m"); - } - - /* - * This has the advantage that interfaces don't need to be up but - * it means that cfg errors will not be caught. - */ - if (cfg->socket_freebind) { - option = true; - if (setsockopt(sfd, IPPROTO_IP, IP_FREEBIND, &option, sizeof(option)) < 0) - error("setsockopt: %m"); - } - - socket_set_low_latency(sfd); - - r = bind(sfd, (struct sockaddr *)&local->local.storage, local->local.addrlen); - if (r < 0) { - error("bind: %m"); - goto error; - } - - r = listen(sfd, 100); - if (r < 0) { - error("listen: %m"); - goto error; - } - - uring_task_set_fd(&local->task, sfd); - uring_accept(&local->task, &local->client, local_accept); - return true; - -error: - if (sfd >= 0) - uring_close(&local->task, sfd); - return false; -} - -void -local_refdump(struct server_local *local) -{ - assert_return(local); - - uring_task_refdump(&local->task); -} - -static void -local_free(struct uring_task *task) -{ - struct server_local *local = container_of(task, struct server_local, task); - - assert_return(task); - - debug(DBG_PROXY, "task %p, local %p", task, local); - list_del(&local->list); - xfree(local); -} - -void -local_delete(struct server_local *local) -{ - assert_return(local); - - uring_task_destroy(&local->task); -} - -struct server_local * -local_new(struct server *server, struct saddr *saddr) -{ - struct server_local *local; - - assert_return(server && saddr, NULL); - - local = zmalloc(sizeof(*local)); - if (!local) { - error("malloc: %m"); - return NULL; - } - - debug(DBG_PROXY, "%s adding local: %s", server->name, saddr->addrstr); - local->local = *saddr; - local->server = server; - uring_task_init(&local->task, "local", &server->task, local_free); - xfree(saddr); - return local; -} - diff --git a/server-proxy.h b/server-proxy.h deleted file mode 100644 index ee3bda3..0000000 --- a/server-proxy.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef fooserverproxyhfoo -#define fooserverproxyhfoo - -struct server_proxy { - struct connection client_conn; - struct uring_task_buf clientbuf; - struct uring_task clienttask; - uint64_t client_bytes; - int cpipe[2]; - int cfd; - - struct connection server_conn; - struct uring_task_buf serverbuf; - struct uring_task servertask; - uint64_t server_bytes; - int spipe[2]; - int sfd; - - bool connecting; - time_t begin; - struct ptimer_task ptask; - struct uring_task task; - struct server *server; - struct list_head list; -}; - -void proxy_refdump(struct server_proxy *proxy); - -void proxy_delete(struct server_proxy *proxy); - -struct server_proxy *proxy_new(struct server *server, struct saddr *client, - int fd); - -struct server_local { - struct saddr local; - struct saddr client; - struct uring_task task; - - struct server *server; - struct list_head list; -}; - -bool local_open(struct server_local *local); - -void local_refdump(struct server_local *local); - -void local_delete(struct server_local *local); - -struct server_local *local_new(struct server *server, struct saddr *saddr); - -#endif diff --git a/server-rcon.c b/server-rcon.c deleted file mode 100644 index 1f8ef70..0000000 --- a/server-rcon.c +++ /dev/null @@ -1,227 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "main.h" -#include "uring.h" -#include "server.h" -#include "server-rcon.h" -#include "rcon-protocol.h" - -static int -rcon_packet_complete(struct uring_task *task, int res) -{ - assert_return(task, -EINVAL); - assert_task_alive_or(DBG_RCON, task, return -EINTR); - - return rcon_protocol_packet_complete(task->tbuf->buf, task->tbuf->len); -} - -static bool -rcon_check_reply(struct uring_task *task, int res, int32_t *id, - int32_t *type, const char **msg) -{ - struct server *server = container_of(task, struct server, rcon_task); - const char *error; - - if (res < 0) { - error("rcon(%s): reading reply failed, res: %i", - server->name, res); - goto error; - } - - if (!rcon_protocol_read_packet(task->tbuf->buf, task->tbuf->len, - id, type, msg, &error)) { - error("rcon(%s): failed to parse packet: %s", - server->name, error); - goto error; - } - - return true; - -error: - uring_task_close_fd(task); - return false; -} - -static void -rcon_stop_reply(struct uring_task *task, int res) -{ - struct server *server = container_of(task, struct server, rcon_task); - int32_t id; - int32_t type; - const char *msg; - - assert_return(task); - assert_task_alive(DBG_RCON, task); - - if (!rcon_check_reply(task, res, &id, &type, &msg)) - return; - - if (id != 2) { - error("rcon(%s): stop cmd failed, reply id (%" PRIi32 ")", - server->name, id); - goto out; - } else if (type != RCON_PACKET_RESPONSE) { - error("rcon(%s): stop cmd failed, reply type (%" PRIi32 ")", - server->name, type); - goto out; - } - - verbose("rcon(%s): stop command sent, reply: %s", server->name, msg); - -out: - uring_task_close_fd(task); -} - -static void -rcon_stop_sent(struct uring_task *task, int res) -{ - struct server *server = container_of(task, struct server, rcon_task); - - assert_return(task); - assert_task_alive(DBG_RCON, task); - - if (res != task->tbuf->len) { - error("rcon(%s): sending stop cmd failed, res: %i", - server->name, res); - uring_task_close_fd(task); - return; - } - - debug(DBG_RCON, "rcon(%s): stop cmd sent", server->name); - uring_tbuf_read_until(task, rcon_packet_complete, rcon_stop_reply); -} - -static void -rcon_login_reply(struct uring_task *task, int res) -{ - struct server *server = container_of(task, struct server, rcon_task); - int32_t id; - int32_t type; - const char *msg; - - assert_return(task); - assert_task_alive(DBG_RCON, task); - - if (!rcon_check_reply(task, res, &id, &type, &msg)) - return; - - if (id != 1) { - error("rcon(%s): login failed, reply id (%" PRIi32 ")", - server->name, id); - goto error; - } else if (type == RCON_PACKET_LOGIN_FAIL) { - error("rcon(%s): login failed, incorrect password", - server->name); - goto error; - } else if (type != RCON_PACKET_LOGIN_OK) { - error("rcon(%s): login failed, reply type (%" PRIi32 ")", - server->name, type); - goto error; - } - - debug(DBG_RCON, "rcon(%s): login successful", server->name); - rcon_protocol_create_packet(task->tbuf->buf, sizeof(task->tbuf->buf), - &task->tbuf->len, 2, RCON_PACKET_COMMAND, - "stop"); - uring_tbuf_write(task, rcon_stop_sent); - return; - -error: - uring_task_close_fd(task); -} - -static void -rcon_login_sent(struct uring_task *task, int res) -{ - struct server *server = container_of(task, struct server, rcon_task); - - assert_return(task); - assert_task_alive(DBG_RCON, task); - - if (res != task->tbuf->len) { - error("rcon(%s): sending login failed, res: %i", - server->name, res); - uring_task_close_fd(task); - return; - } - - debug(DBG_RCON, "rcon(%s): login sent", server->name); - uring_tbuf_read_until(task, rcon_packet_complete, rcon_login_reply); -} - -static void -rcon_connected_cb(struct connection *conn, bool connected) -{ - struct server *server = container_of(conn, struct server, rcon_conn); - - assert_return(conn); - assert_task_alive(DBG_RCON, &server->rcon_task); - - if (!connected) { - error("rcon (%s): connection failed", server->name); - return; - } - - rcon_protocol_create_packet(server->rcon_tbuf.buf, - sizeof(server->rcon_tbuf.buf), - &server->rcon_tbuf.len, 1, - RCON_PACKET_LOGIN, - server->rcon_password); - uring_tbuf_write(&server->rcon_task, rcon_login_sent); -} - -static void -rcon_free(struct uring_task *task) -{ - struct server *server = container_of(task, struct server, rcon_task); - - assert_return(task); - - debug(DBG_RCON, "task %p, server %s (%p)", task, server->name, server); -} - -void -rcon_stop(struct server *server) -{ - assert_return(server && !list_empty(&server->rcons) && !empty_str(server->rcon_password)); - assert_task_alive(DBG_RCON, &server->rcon_task); - - connect_any(&server->rcon_task, &server->rcons, &server->rcon_conn, rcon_connected_cb); -} - -void -rcon_refdump(struct server *server) -{ - assert_return(server); - - uring_task_refdump(&server->rcon_task); -} - -void -rcon_delete(struct server *server) -{ - assert_return(server); - - debug(DBG_RCON, "closing fd %i", server->rcon_task.fd); - uring_task_destroy(&server->rcon_task); -} - -void -rcon_init(struct server *server) -{ - assert_return(server); - - uring_task_init(&server->rcon_task, "rcon", &server->task, rcon_free); - uring_task_set_buf(&server->rcon_task, &server->rcon_tbuf); -} - diff --git a/server-rcon.h b/server-rcon.h deleted file mode 100644 index 6625f25..0000000 --- a/server-rcon.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef fooserverrconhfoo -#define fooserverrconhfoo - -void rcon_stop(struct server *server); - -void rcon_refdump(struct server *server); - -void rcon_delete(struct server *server); - -void rcon_init(struct server *server); - -#endif diff --git a/server.c b/server.c deleted file mode 100644 index de42721..0000000 --- a/server.c +++ /dev/null @@ -1,837 +0,0 @@ -#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 "utils.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; -} diff --git a/server.h b/server.h deleted file mode 100644 index ff4c28e..0000000 --- a/server.h +++ /dev/null @@ -1,128 +0,0 @@ -#ifndef fooserverhfoo -#define fooserverhfoo - -enum server_state { - SERVER_STATE_INIT = 0, - SERVER_STATE_CFG_OK = 1, - SERVER_STATE_RUNNING = 2, - SERVER_STATE_STOPPED = 3, - SERVER_STATE_DEAD = 4, -}; - -enum server_type { - SERVER_TYPE_UNDEFINED, - SERVER_TYPE_ANNOUNCE, - SERVER_TYPE_PROXY, -}; - -enum server_stop_method { - SERVER_STOP_METHOD_UNDEFINED, - SERVER_STOP_METHOD_RCON, - SERVER_STOP_METHOD_SYSTEMD, - SERVER_STOP_METHOD_EXEC, -}; - -enum server_start_method { - SERVER_START_METHOD_UNDEFINED, - SERVER_START_METHOD_SYSTEMD, - SERVER_START_METHOD_EXEC, -}; - -struct server { - enum server_type type; - char *name; - char *pretty_name; - uint16_t announce_port; - struct list_head locals; - struct list_head remotes; - struct list_head proxys; - struct list_head rcons; - struct list_head dnslookups; - enum server_state state; - - enum server_stop_method stop_method; - enum server_start_method start_method; - - /* For calling external start/stop executables */ - char *stop_exec; - char *start_exec; - struct uring_task exec_task; - - /* For systemd services */ - char *systemd_service; - char *systemd_obj; - - /* For rcon connections */ - char *rcon_password; - struct connection rcon_conn; - struct uring_task rcon_task; - struct uring_task_buf rcon_tbuf; - - /* For announce messages */ - struct uring_task ann_task; - struct uring_task_buf ann_buf; - - /* For checking idle status */ - struct uring_task idle_task; - struct connection idle_conn; - struct uring_task_buf idle_buf; - unsigned idle_timeout; - unsigned idle_count; - - /* For reading config files */ - struct uring_task task; - struct uring_task_buf tbuf; - - struct list_head list; -}; - -void server_refdump(struct server *server); - -void server_delete(struct server *server); - -void server_delete_by_name(const char *name); - -bool server_start(struct server *server); - -bool server_stop(struct server *server); - -void server_set_active_players(struct server *server, int count); - -bool server_idle_check(struct server *server); - -bool server_announce(struct server *server, int fd); - -bool server_commit(struct server *server); - -bool server_add_remote(struct server *server, struct saddr *remote); - -bool server_add_local(struct server *server, struct saddr *saddr); - -bool server_add_rcon(struct server *server, struct saddr *rcon); - -bool server_set_rcon_password(struct server *server, const char *password); - -bool server_set_systemd_service(struct server *server, const char *service); - -bool server_set_stop_method(struct server *server, - enum server_stop_method stop_method); - -bool server_set_start_method(struct server *server, - enum server_start_method start_method); - -bool server_set_stop_exec(struct server *server, const char *cmd); - -bool server_set_start_exec(struct server *server, const char *cmd); - -bool server_set_idle_timeout(struct server *server, uint16_t timeout); - -bool server_set_port(struct server *server, uint16_t port); - -bool server_set_type(struct server *server, enum server_type type); - -bool server_set_pretty_name(struct server *server, const char *pretty_name); - -struct server *server_new(const char *name); - -#endif - diff --git a/shared/meson.build b/shared/meson.build new file mode 100644 index 0000000..cbb7dca --- /dev/null +++ b/shared/meson.build @@ -0,0 +1,22 @@ +srcs_libshared = [ + 'rcon-protocol.c', +] + +inc_libshared = include_directories('.') + +deps_libshared = [] + +lib_libshared = static_library( + 'shared', + srcs_libshared, + pic: true, + install: false, + dependencies: deps_libshared, +) + +dep_libshared = declare_dependency( + link_with: lib_libshared, + dependencies: deps_libshared, + include_directories: inc_libshared, +) + diff --git a/shared/rcon-protocol.c b/shared/rcon-protocol.c new file mode 100644 index 0000000..0ea1245 --- /dev/null +++ b/shared/rcon-protocol.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include + +#include "rcon-protocol.h" + +static int32_t +read_int(const char **pos, size_t *len) +{ + uint32_t val; + const char *p; + + if (!pos || !*pos) + return 0; + + if (len && *len < RCON_INT_LEN) + return 0; + + p = *pos; + val = ((uint8_t)p[0] << 0); + val += ((uint8_t)p[1] << 8); + val += ((uint8_t)p[2] << 16); + val += ((uint8_t)p[3] << 24); + + *pos += RCON_INT_LEN; + if (len) + *len -= RCON_INT_LEN; + + return (int32_t)val; +} + +static void +write_int(char **pos, size_t *len, int32_t orig) +{ + uint32_t val = (uint32_t)orig; + char *p; + + if (!pos || !*pos) + return; + + p = *pos; + p[0] = (val >> 0) & 0xff; + p[1] = (val >> 8) & 0xff; + p[2] = (val >> 16) & 0xff; + p[3] = (val >> 24) & 0xff; + + *pos += RCON_INT_LEN; + if (len) + *len += RCON_INT_LEN; +} + +static void +write_str(char **pos, size_t *len, const char *str, size_t slen) +{ + if (!pos || !*pos || !str || *str == '\0' || slen < 1) + return; + + memcpy(*pos, str, slen); + *pos += slen; + if (len) + *len += slen; +} + +static void +write_end(char **pos, size_t *len) +{ + char *p; + + if (!pos || !*pos) + return; + + p = *pos; + p[0] = 0x00; + p[1] = 0x00; + + *pos += RCON_END_LEN; + if (len) + *len += RCON_END_LEN; +} + +bool +rcon_protocol_create_packet(char *buf, size_t len, size_t *rlen, int32_t reqid, + enum rcon_packet_type type, const char *msg) +{ + size_t plen, msglen; + char *pos; + + if (!buf || !rlen || !msg || msg[0] == '\0') + return false; + + msglen = strlen(msg); + if (len < rcon_protocol_packet_len(msglen)) + return false; + + /* Body */ + pos = buf + RCON_HDR_LEN; + plen = RCON_HDR_LEN; + write_int(&pos, &plen, reqid); + write_int(&pos, &plen, type); + write_str(&pos, &plen, msg, msglen); + write_end(&pos, &plen); + + /* Header (length of body) */ + pos = buf; + write_int(&pos, NULL, plen - RCON_HDR_LEN); + + *rlen = plen; + return true; +} + +/* + * Returns: + * < 0 = error + * 0 = packet not complete + * > 0 = packet complete (length) + */ +int32_t +rcon_protocol_packet_complete(const char *buf, size_t len) +{ + const char *pos; + int32_t plen; + + if (!buf) + return -1; + + if (len < RCON_PKT_MIN_LEN) + return 0; + + pos = buf; + plen = read_int(&pos, NULL) + RCON_HDR_LEN; + + if (plen < RCON_HDR_LEN) + /* Read a negative int */ + return -1; + + if (len < plen) + return 0; + else + return plen; +} + +bool +rcon_protocol_read_packet(const char *buf, size_t len, int32_t *id, int32_t *type, + const char **rmsg, const char **error) +{ + const char *pos; + int32_t plen; + + if (!error) + return false; + + if (!buf || !id || !type || !rmsg) { + *error = "invalid arguments"; + return false; + } + + plen = rcon_protocol_packet_complete(buf, len); + *error = "invalid packet length"; + if (plen < RCON_PKT_MIN_LEN) + return false; + + pos = buf + RCON_HDR_LEN; + len -= RCON_HDR_LEN; + + *id = read_int(&pos, &len); + *type = read_int(&pos, &len); + *rmsg = NULL; + + if (len < RCON_END_LEN) + return false; + else if (len > RCON_END_LEN) { + *rmsg = pos; + pos += len - RCON_END_LEN; + len = RCON_END_LEN; + } + + if (pos[0] != '\0' || pos[1] != '\0') { + *error = "invalid trailer"; + return false; + } + + *error = NULL; + return true; +} diff --git a/shared/rcon-protocol.h b/shared/rcon-protocol.h new file mode 100644 index 0000000..35997c4 --- /dev/null +++ b/shared/rcon-protocol.h @@ -0,0 +1,41 @@ +#ifndef foorconprotocolhfoo +#define foorconprotocolhfoo + +#include +#include + +enum rcon_packet_type { + RCON_PACKET_LOGIN = 3, + RCON_PACKET_LOGIN_OK = 2, + RCON_PACKET_LOGIN_FAIL = -1, + RCON_PACKET_COMMAND = 2, + RCON_PACKET_RESPONSE = 0, +}; + +#define RCON_INT_LEN 4 + +#define RCON_END_LEN 2 + +#define RCON_HDR_LEN 4 + +/* header + reqid + type + end */ +#define RCON_PKT_MIN_LEN (RCON_HDR_LEN + 2 * RCON_INT_LEN + RCON_END_LEN) + +static inline size_t +rcon_protocol_packet_len(size_t msglen) +{ + /* header + reqid + type + msg + end */ + return (RCON_PKT_MIN_LEN + msglen); +} + +bool rcon_protocol_create_packet(char *buf, size_t len, size_t *rlen, + int32_t reqid, enum rcon_packet_type type, + const char *msg); + +int32_t rcon_protocol_packet_complete(const char *buf, size_t len); + +bool rcon_protocol_read_packet(const char *buf, size_t len, int32_t *id, + int32_t *type, const char **rmsg, + const char **error); + +#endif diff --git a/signal-handler.c b/signal-handler.c deleted file mode 100644 index 67c2e0b..0000000 --- a/signal-handler.c +++ /dev/null @@ -1,195 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include - -#include "main.h" -#include "signal-handler.h" -#include "uring.h" -#include "server.h" -#include "server-config.h" -#include "config-parser.h" -#include "igmp.h" -#include "announce.h" -#include "idle.h" -#include "ptimer.h" - -struct signal_ev { - struct uring_task task; - struct uring_task_buf tbuf; - int pipe[2]; -}; - -static void -signal_delete() -{ - assert_return(cfg->signal); - - uring_task_destroy(&cfg->signal->task); -} - -static void -signalfd_read(struct uring_task *task, int res) -{ - struct signal_ev *signal = container_of(task, struct signal_ev, task); - struct server *server, *stmp; - static unsigned count = 0; - siginfo_t *si; - - assert_return(task); - assert_task_alive(DBG_SIG, task); - - si = (siginfo_t *)task->tbuf->buf; - if (res != sizeof(*si)) - die("error in signalfd (%i)", res); - - switch (si->si_signo) { - case SIGUSR1: { - struct dns_async *dns; - - debug(DBG_SIG, "Got a SIGUSR1"); - if (si->si_code != SI_ASYNCNL || !si->si_ptr) { - error("SIGUSR1: unexpected values in siginfo"); - goto out; - } - - dns = si->si_ptr; - if (!dns->cb) { - error("DNS callback not set"); - goto out; - } - - debug(DBG_DNS, "DNS lookup complete, dns: %p, dns->cb: %p", - dns, dns->cb); - dns->cb(dns); - break; - } - - case SIGUSR2: - debug(DBG_SIG, "got a SIGUSR2"); - dump_tree(); - break; - - case SIGTERM: - debug(DBG_SIG, "Got a SIGINT/SIGHUP"); - verbose("got a signal to quit"); - sd_notifyf(0, "STOPPING=1\nSTATUS=Received signal, exiting"); - exit(EXIT_SUCCESS); - break; - - case SIGINT: - case SIGHUP: - count++; - if (count > 5) { - dump_tree(); - exit(EXIT_FAILURE); - } - - verbose("got a signal to dump tree"); - sd_notifyf(0, "STOPPING=1\nSTATUS=Received signal, exiting"); - dump_tree(); - signal_delete(); - ptimer_delete(); - igmp_delete(); - announce_delete(); - idle_delete(); - server_cfg_monitor_delete(); - list_for_each_entry_safe(server, stmp, &cfg->servers, list) - server_delete(server); - uring_delete(); - return; - - default: - error("got an unknown signal: %i", si->si_signo); - break; - } - -out: - uring_tbuf_read(&signal->task, signalfd_read); -} - -static void -hack_signal_handler(int signum, siginfo_t *si, void *ucontext) -{ - ssize_t r; - - assert_return(signum > 0 && si); - - r = write(cfg->signal->pipe[PIPE_WR], si, sizeof(*si)); - if (r != sizeof(*si)) - error("write: %zi\n", r); - -} - -static void -signal_free(struct uring_task *task) -{ - struct signal_ev *signal = container_of(task, struct signal_ev, task); - - assert_return(task); - - debug(DBG_SIG, "called"); - close(signal->pipe[PIPE_WR]); - cfg->signal = NULL; - xfree(signal); -} - -void -signal_refdump() -{ - assert_return(cfg->signal); - - uring_task_refdump(&cfg->signal->task); -} - -void -signal_init() -{ - //sigset_t mask; - struct signal_ev *signal; - - assert_return(!cfg->signal); - - signal = zmalloc(sizeof(*signal)); - if (!signal) - die("malloc: %m"); - - if (pipe2(signal->pipe, O_CLOEXEC) < 0) - die("pipe2: %m"); - - /* - sigfillset(&mask); - if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) - die("sigprocmask: %m"); - - sfd = signalfd(-1, &mask, SFD_CLOEXEC); - if (sfd < 0) - die("signalfd: %m"); - */ - - struct sigaction action; - sigfillset(&action.sa_mask); - action.sa_sigaction = hack_signal_handler; - action.sa_flags = SA_SIGINFO; - - sigaction(SIGINT, &action, NULL); - sigaction(SIGHUP, &action, NULL); - sigaction(SIGTERM, &action, NULL); - sigaction(SIGUSR1, &action, NULL); - sigaction(SIGUSR2, &action, NULL); - - action.sa_handler = SIG_IGN; - action.sa_flags = 0; - sigaction(SIGPIPE, &action, NULL); - - debug(DBG_SIG, "using pipe fds %i -> %i", - signal->pipe[PIPE_WR], signal->pipe[PIPE_RD]); - uring_task_init(&signal->task, "signal", uring_parent(), signal_free); - uring_task_set_fd(&signal->task, signal->pipe[PIPE_RD]); - uring_task_set_buf(&signal->task, &signal->tbuf); - cfg->signal = signal; - uring_tbuf_read(&signal->task, signalfd_read); -} - diff --git a/signal-handler.h b/signal-handler.h deleted file mode 100644 index e0140b3..0000000 --- a/signal-handler.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef foosignalhfoo -#define foosignalhfoo - -void signal_refdump(); - -void signal_init(); - -#endif diff --git a/systemd.c b/systemd.c deleted file mode 100644 index a44b0d8..0000000 --- a/systemd.c +++ /dev/null @@ -1,219 +0,0 @@ -#include -#include -#include - -#include "main.h" -#include "server.h" -#include "systemd.h" - -#define SYSTEMD_DBUS_SERVICE "org.freedesktop.systemd1" -#define SYSTEMD_DBUS_INTERFACE "org.freedesktop.systemd1.Unit" -#define SYSTEMD_DBUS_PATH_PREFIX "/org/freedesktop/systemd1/unit/" - -static inline char -tohex(uint8_t val) -{ - static const char hex[] = "0123456789abcdef"; - - return hex[val & 0x0f]; -} - -/* - * Creates an escaped D-Bus object path for a given systemd service - * - * Escaping rules are documented here: - * https://dbus.freedesktop.org/doc/dbus-specification.html - * - * Essentially, everyting but a-z, A-Z, 0-9 is replaced by _xx where xx is - * the hexadecimal value of the character. - * - * Example: minecraft@world1.service -> minecraft_40world1_2eservice - */ -char * -systemd_object_path(const char *service) -{ - char *r; - char *d; - const char *s; - - assert_return(service && !empty_str(service), NULL); - - r = zmalloc(strlen(SYSTEMD_DBUS_PATH_PREFIX) + strlen(service) * 3 + 1); - if (!r) - return NULL; - - memcpy(r, SYSTEMD_DBUS_PATH_PREFIX, strlen(SYSTEMD_DBUS_PATH_PREFIX)); - d = r + strlen(SYSTEMD_DBUS_PATH_PREFIX); - - for (s = service; *s; s++) { - if ((*s >= 'a' && *s <= 'z') || - (*s >= 'A' && *s <= 'Z') || - (*s >= '0' && *s <= '9')) { - *(d++) = *s; - continue; - } - - *(d++) = '_'; - *(d++) = tohex(*s >> 4); - *(d++) = tohex(*s); - } - - *d = '\0'; - return r; -} - -void -systemd_delete() -{ - assert_return_silent(cfg->sd_bus); - - sd_bus_unref(cfg->sd_bus); - cfg->sd_bus = NULL; -} - -static sd_bus * -get_bus() -{ - int r; - - if (cfg->sd_bus_failed) - return NULL; - - if (!cfg->sd_bus) { - r = sd_bus_open_user(&cfg->sd_bus); - if (r < 0) { - error("failed to connect to user system bus: %s", strerror(-r)); - cfg->sd_bus_failed = true; - return NULL; - } - } - - return cfg->sd_bus; -} - -/* - * Check if a systemd service is running. - * - * This is equivalent to (assuming service minecraft@world1): - * gdbus call --session - * --dest org.freedesktop.systemd1 - * --object-path /org/freedesktop/systemd1/unit/minecraft_40world1_2eservice - * --method org.freedesktop.DBus.Properties.Get - * "org.freedesktop.systemd1.Unit" - * "ActiveState" - */ -bool -systemd_service_running(struct server *server) -{ - sd_bus *bus = get_bus(); - sd_bus_error error = SD_BUS_ERROR_NULL; - char *status = NULL; - bool running = false; - int r; - - assert_return(server && bus && server->systemd_service && server->systemd_obj, false); - - r = sd_bus_get_property_string(bus, - SYSTEMD_DBUS_SERVICE, - server->systemd_obj, - SYSTEMD_DBUS_INTERFACE, - "ActiveState", - &error, - &status); - if (r < 0) { - error("failed to get status for service %s (%s): %s", - server->systemd_service, server->systemd_obj, error.message); - goto out; - } - - if (streq(status, "active")) { - running = true; - debug(DBG_SYSD, "systemd service %s (%s) is active", - server->systemd_service, server->systemd_obj); - } else - debug(DBG_SYSD, "systemd service %s (%s) is not active", - server->systemd_service, server->systemd_obj); - -out: - free(status); - sd_bus_error_free(&error); - return running; -} - -static bool -systemd_service_action(struct server *server, const char *action) -{ - sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus_message *m = NULL; - sd_bus *bus = get_bus(); - const char *path; - bool performed = false; - int r; - - assert_return(server && bus && server->systemd_service && server->systemd_obj && action, false); - - r = sd_bus_call_method(bus, - SYSTEMD_DBUS_SERVICE, - server->systemd_obj, - SYSTEMD_DBUS_INTERFACE, - action, - &error, - &m, - "s", - "fail"); - if (r < 0) { - error("failed to perform action %s on systemd service %s: %s", - action, server->systemd_service, error.message); - goto out; - } - - r = sd_bus_message_read(m, "o", &path); - if (r < 0) { - error("failed to parse response message: %s", strerror(-r)); - goto out; - } - - verbose("action %s queued for service %s", - action, server->systemd_service); - performed = true; - -out: - sd_bus_error_free(&error); - sd_bus_message_unref(m); - return performed; -} - -/* - * Stop systemd service. - * - * This is equivalent to (assuming service minecraft@world1): - * gdbus call --session - * --dest org.freedesktop.systemd1 - * --object-path /org/freedesktop/systemd1/unit/minecraft_40world1_2eservice - * --method org.freedesktop.systemd1.Unit.Stop "fail" - */ -bool -systemd_service_stop(struct server *server) -{ - assert_return(server, false); - - return systemd_service_action(server, "Stop"); -} - -/* - * Start systemd service. - * - * This is equivalent to (assuming service minecraft@world1): - * gdbus call --session - * --dest org.freedesktop.systemd1 - * --object-path /org/freedesktop/systemd1/unit/minecraft_40world1_2eservice - * --method org.freedesktop.systemd1.Unit.Start "fail" - */ -bool -systemd_service_start(struct server *server) -{ - assert_return(server, false); - - return systemd_service_action(server, "Start"); -} - diff --git a/systemd.h b/systemd.h deleted file mode 100644 index d455044..0000000 --- a/systemd.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef foosystemdhfoo -#define foosystemdhfoo - -char *systemd_object_path(const char *service); - -void systemd_delete(); - -bool systemd_service_running(struct server *server); - -bool systemd_service_stop(struct server *server); - -bool systemd_service_start(struct server *server); - -#endif diff --git a/uring.c b/uring.c deleted file mode 100644 index e979471..0000000 --- a/uring.c +++ /dev/null @@ -1,759 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include - -#include "main.h" -#include "uring.h" - -struct uring_ev { - struct io_uring uring; - struct io_uring_params uring_params; - struct uring_task task; - - /* for testing if the kernel supports splice */ - int pipe[2]; - int tfd; -}; - -enum cqe_type { - CQE_TYPE_NORMAL = 0x0, - CQE_TYPE_CANCEL = 0x1, - CQE_TYPE_CLOSE = 0x2, - CQE_TYPE_POLL_CANCEL = 0x3 -}; - -#define CQE_TYPE_PTR_MASK 0x3 - -uint64_t sqe_count = 0; -uint64_t cqe_count = 0; - -static struct io_uring_sqe * -get_sqe(struct uring_task *task) -{ - struct io_uring_sqe *sqe; - - assert_die(task, "invalid arguments"); - - sqe = io_uring_get_sqe(&cfg->uring->uring); - if (!sqe) { - io_uring_submit(&cfg->uring->uring); - sqe = io_uring_get_sqe(&cfg->uring->uring); - if (!sqe) - die("failed to get an sqe!"); - } - - sqe_count++; - uring_task_get(task); - return sqe; -} - -void -uring_task_refdump(struct uring_task *task) -{ - char buf[4096]; - struct uring_task *tmp; - - assert_return(task); - - if (!debug_enabled(DBG_REF)) - return; - - buf[0] = '\0'; - for (tmp = task; tmp; tmp = tmp->parent) { - size_t prefix; - char *dst; - - if (tmp->parent) - prefix = strlen("->") + strlen(tmp->name); - else - prefix = strlen(tmp->name); - - memmove(&buf[prefix], &buf[0], strlen(buf) + 1); - - dst = &buf[0]; - if (tmp->parent) { - *dst++ = '-'; - *dst++ = '>'; - } - - memcpy(dst, tmp->name, strlen(tmp->name)); - } - - info("%s (0x%p parent 0x%p free 0x%p fd %i ref %u)", - buf, task, task->parent, task->free, task->fd, - task->refcount); -} - -/* - * Similar to uring_task_put, but can be called from other tasks - * while the task is active. - */ -void -uring_task_destroy(struct uring_task *task) -{ - assert_return(task); - assert_return_silent(!task->dead); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - if (task->fd >= 0) { - struct io_uring_sqe *sqe; - - sqe = get_sqe(task); - io_uring_prep_cancel(sqe, task, 0); - io_uring_sqe_set_data(sqe, (void *)((uintptr_t)task | CQE_TYPE_CANCEL)); - } - - task->dead = true; - uring_task_put(task); -} - -void -uring_task_put(struct uring_task *task) -{ - struct uring_task *parent; - - assert_return(task); - - debug(DBG_REF, "task %s (%p), refcount %u -> %u", - task->name, task, task->refcount, task->refcount - 1); - - task->refcount--; - - if (task->refcount > 0) - return; - - if (task->refcount < 0) - error("Negative refcount!"); - - if (task->fd >= 0) { - uring_task_close_fd(task); - /* We'll be called again once the fd is closed */ - return; - } - - parent = task->parent; - if (task->free) - task->free(task); - - if (parent) { - debug(DBG_REF, "putting parent %s (%p)", parent->name, parent); - uring_task_put(parent); - } -} - -void -uring_task_get(struct uring_task *task) -{ - assert_return(task); - - debug(DBG_REF, "task %s (%p), refcount %u -> %u", - task->name, task, task->refcount, task->refcount + 1); - - if (task->refcount < 0) - error("Negative refcount!"); - - task->refcount++; -} - -void -uring_task_set_buf(struct uring_task *task, struct uring_task_buf *tbuf) -{ - assert_return(task && tbuf); - - debug(DBG_UR, "task %s (%p), buf %p, refcount %u", - task->name, task, tbuf, task->refcount); - - /* iov_len and msg_namelen are set at send/receive time */ - tbuf->iov.iov_base = tbuf->buf; - tbuf->msg.msg_name = &task->saddr.storage; - tbuf->msg.msg_iov = &tbuf->iov; - tbuf->msg.msg_iovlen = 1; - tbuf->msg.msg_control = NULL; - tbuf->msg.msg_controllen = 0; - tbuf->msg.msg_flags = 0; - task->tbuf = tbuf; -} - -void -uring_task_set_fd(struct uring_task *task, int fd) -{ - assert_return(task); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, fd, task->refcount); - - task->fd = fd; -} - -void -uring_task_close_fd(struct uring_task *task) -{ - assert_return(task); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - if (task->fd < 0) - return; - - uring_close(task, task->fd); - task->fd = -1; -} - -struct uring_task * -uring_parent() -{ - assert_die(cfg->uring, "invalid arguments"); - - return &cfg->uring->task; -} - -void -uring_task_init(struct uring_task *task, const char *name, - struct uring_task *parent, void (*free)(struct uring_task *)) -{ - static bool first = true; - - assert_die(task && !empty_str(name) && free, "invalid arguments"); - - if (first) - first = false; - else - assert_die(parent, "called without a parent task"); - - task->refcount = 1; - task->fd = -1; - task->parent = parent; - task->free = free; - task->dead = false; - task->name = name; - task->tbuf = NULL; - - if (task->parent) { - debug(DBG_REF, "task %s (%p), refcount %u, " - "getting parent %s (%p), refcount %u", - task->name, task, task->refcount, - task->parent->name, task->parent, task->parent->refcount); - uring_task_get(task->parent); - } -} - -void -uring_close(struct uring_task *task, int fd) -{ - struct io_uring_sqe *sqe; - - assert_return(task && fd >= 0); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - sqe = get_sqe(task); - io_uring_prep_close(sqe, fd); - io_uring_sqe_set_data(sqe, (void *)((uintptr_t)task | CQE_TYPE_CLOSE)); -} - -static void -uring_tbuf_write_cb(struct uring_task *task, int res) -{ - int r; - - assert_return(task && task->tbuf && task->final_cb); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - if (res < 0) { - r = res; - goto finished; - } - - /* We wrote some more data */ - task->tbuf->done += res; - if (task->tbuf->done >= task->tbuf->len || res == 0) { - r = task->tbuf->len; - goto finished; - } - - uring_write(task, task->tbuf->buf + task->tbuf->done, - task->tbuf->len - task->tbuf->done, - uring_tbuf_write_cb); - return; - -finished: - task->final_cb(task, r); -} - -void -uring_tbuf_write(struct uring_task *task, utask_cb_t final_cb) -{ - assert_return(task && task->fd >= 0 && task->tbuf && task->tbuf->len > 0); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - task->tbuf->done = 0; - task->final_cb = final_cb; - uring_write(task, &task->tbuf->buf, task->tbuf->len, uring_tbuf_write_cb); -} - -void -uring_write(struct uring_task *task, void *buf, size_t len, utask_cb_t cb) -{ - struct io_uring_sqe *sqe; - - assert_return(task && buf && len > 0 && cb && task->fd >= 0); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - sqe = get_sqe(task); - task->cb = cb; - io_uring_prep_write(sqe, task->fd, buf, len, 0); - io_uring_sqe_set_data(sqe, task); -} - -static void -uring_tbuf_read_until_cb(struct uring_task *task, int res) -{ - int r; - - assert_return(task && task->tbuf && task->final_cb && task->is_complete_cb); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - if (res < 0) { - r = res; - goto finished; - } - - task->tbuf->len += res; - r = task->is_complete_cb(task, res); - if (r < 0) { - r = res; - goto finished; - } else if (r > 0) { - r = task->tbuf->len; - goto finished; - } - - /* Need to read more */ - if (task->tbuf->len >= sizeof(task->tbuf->buf)) { - r = E2BIG; - goto finished; - } - - uring_read_offset(task, task->tbuf->buf + task->tbuf->len, - sizeof(task->tbuf->buf) - task->tbuf->len, - task->tbuf->len, uring_tbuf_read_until_cb); - return; - -finished: - task->final_cb(task, r); -} - -void -uring_tbuf_read_until(struct uring_task *task, - rutask_cb_t is_complete_cb, utask_cb_t final_cb) -{ - assert_return(task && task->fd >= 0 && task->tbuf && is_complete_cb && final_cb); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - task->tbuf->len = 0; - task->is_complete_cb = is_complete_cb; - task->final_cb = final_cb; - uring_read(task, &task->tbuf->buf, sizeof(task->tbuf->buf), - uring_tbuf_read_until_cb); -} - -static int -uring_tbuf_eof(struct uring_task *task, int res) -{ - assert_return(task && task->tbuf, -EINVAL); - assert_task_alive_or(DBG_UR, task, return -EINTR); - - if (task->tbuf->len + 1 >= sizeof(task->tbuf->buf)) - return -E2BIG; - - if (res > 0) - return 0; - - task->tbuf->buf[task->tbuf->len] = '\0'; - task->tbuf->len++; - return 1; -} - -void -uring_tbuf_read_until_eof(struct uring_task *task, - utask_cb_t final_cb) -{ - assert_return(task && task->tbuf && final_cb); - - uring_tbuf_read_until(task, uring_tbuf_eof, final_cb); -} - -static int -uring_tbuf_have_data(struct uring_task *task, int res) -{ - assert_return(task, -EINVAL); - - if (res < 0) - return res; - else - return 1; -} - -void -uring_tbuf_read(struct uring_task *task, utask_cb_t final_cb) -{ - assert_return(task && final_cb); - - uring_tbuf_read_until(task, uring_tbuf_have_data, final_cb); -} - -void -uring_read_offset(struct uring_task *task, void *buf, size_t len, off_t offset, utask_cb_t cb) -{ - struct io_uring_sqe *sqe; - - assert_return(task && buf && len > 0 && task->fd >= 0); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - sqe = get_sqe(task); - task->cb = cb; - io_uring_prep_read(sqe, task->fd, buf, len, offset); - io_uring_sqe_set_data(sqe, task); -} - -void -uring_openat(struct uring_task *task, const char *path, utask_cb_t cb) -{ - struct io_uring_sqe *sqe; - - assert_return(task && !empty_str(path) && cb); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - sqe = get_sqe(task); - task->cb = cb; - io_uring_prep_openat(sqe, AT_FDCWD, path, O_RDONLY | O_CLOEXEC, 0); - io_uring_sqe_set_data(sqe, task); -} - -void -uring_tbuf_recvmsg(struct uring_task *task, utask_cb_t cb) -{ - struct io_uring_sqe *sqe; - - assert_return(task && task->fd >= 0 && task->tbuf && cb); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - sqe = get_sqe(task); - task->tbuf->done = 0; - task->tbuf->len = 0; - task->tbuf->iov.iov_len = sizeof(task->tbuf->buf); - task->tbuf->msg.msg_namelen = task->saddr.addrlen; - task->cb = cb; - io_uring_prep_recvmsg(sqe, task->fd, &task->tbuf->msg, 0); - io_uring_sqe_set_data(sqe, task); -} - -void -uring_tbuf_sendmsg(struct uring_task *task, utask_cb_t cb) -{ - struct io_uring_sqe *sqe; - - assert_return(task && task->fd >= 0 && task->tbuf && cb); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - sqe = get_sqe(task); - task->tbuf->done = 0; - task->tbuf->iov.iov_len = task->tbuf->len; - task->tbuf->msg.msg_namelen = task->saddr.addrlen; - task->cb = cb; - io_uring_prep_sendmsg(sqe, task->fd, &task->tbuf->msg, 0); - io_uring_sqe_set_data(sqe, task); -} - -void -uring_connect(struct uring_task *task, struct saddr *saddr, utask_cb_t cb) -{ - struct io_uring_sqe *sqe; - - assert_return(task && task->fd >= 0 && saddr && cb); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - sqe = get_sqe(task); - task->cb = cb; - io_uring_prep_connect(sqe, task->fd, (struct sockaddr *)&saddr->storage, saddr->addrlen); - io_uring_sqe_set_data(sqe, task); -} - -void -uring_accept(struct uring_task *task, struct saddr *saddr, utask_cb_t cb) -{ - struct io_uring_sqe *sqe; - - assert_return(task && task->fd >= 0 && saddr && cb); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - sqe = get_sqe(task); - saddr->addrlen = sizeof(saddr->storage); - task->cb = cb; - io_uring_prep_accept(sqe, task->fd, (struct sockaddr *)&saddr->storage, &saddr->addrlen, SOCK_CLOEXEC); - io_uring_sqe_set_data(sqe, task); -} - -void -uring_splice(struct uring_task *task, int fd_in, int fd_out, utask_cb_t cb) -{ - struct io_uring_sqe *sqe; - - assert_return(task && fd_in >= 0 && fd_out >= 0 && cb); - - debug(DBG_UR, "task %s (%p), fd_in %i, fd_out %i, refcount %u", - task->name, task, fd_in, fd_out, task->refcount); - - sqe = get_sqe(task); - task->cb = cb; - io_uring_prep_splice(sqe, fd_in, -1, fd_out, -1, 4096, SPLICE_F_MOVE); - io_uring_sqe_set_data(sqe, task); -} - -void -uring_poll(struct uring_task *task, short poll_mask, utask_cb_t cb) -{ - struct io_uring_sqe *sqe; - - assert_return(task && task->fd >= 0 && poll_mask && cb); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - sqe = get_sqe(task); - task->cb = cb; - io_uring_prep_poll_add(sqe, task->fd, poll_mask); - io_uring_sqe_set_data(sqe, task); -} - -void -uring_poll_cancel(struct uring_task *task) -{ - struct io_uring_sqe *sqe; - - assert_return(task); - - if (task->fd < 0) { - /* not an error, no need to print error msg */ - return; - } - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - sqe = get_sqe(task); - task->dead = true; - io_uring_prep_poll_remove(sqe, task); - io_uring_sqe_set_data(sqe, (void *)((uintptr_t)task | CQE_TYPE_POLL_CANCEL)); -} - -static void -uring_free(struct uring_task *task) -{ - struct uring_ev *uring = container_of(task, struct uring_ev, task); - - assert_return(task); - - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - io_uring_queue_exit(&uring->uring); - cfg->uring = NULL; - xfree(uring); -} - -void -uring_refdump() -{ - assert_return(cfg->uring); - - uring_task_refdump(&cfg->uring->task); -} - -void -uring_delete() -{ - struct uring_task *task; - - assert_return(cfg->uring); - - task = &cfg->uring->task; - debug(DBG_UR, "task %s (%p), fd %i, refcount %u", - task->name, task, task->fd, task->refcount); - - uring_task_put(task); -} - -static void -uring_splice_test_cb(struct uring_task *task, int res) -{ - struct uring_ev *uring = container_of(task, struct uring_ev, task); - - assert_die(task && uring == cfg->uring, "splice test failed"); - - uring_close(task, uring->tfd); - uring_close(task, uring->pipe[PIPE_RD]); - uring_close(task, uring->pipe[PIPE_WR]); - - uring->tfd = -1; - uring->pipe[PIPE_RD] = -1; - uring->pipe[PIPE_WR] = -1; - - if (res >= 0) { - cfg->splice_supported = true; - debug(DBG_UR, "splice supported"); - } else if (res == -EINVAL) - debug(DBG_UR, "splice not supported"); - else - error("splice check failed: %i\n", res); -} - -void -uring_init() -{ - struct uring_ev *uring; - - assert_return(!cfg->uring); - - uring = zmalloc(sizeof(*uring)); - if (!uring) - die("malloc: %m"); - - if (io_uring_queue_init_params(4096, &uring->uring, &uring->uring_params) < 0) - die("io_uring_queue_init_params"); - - debug(DBG_UR, "uring initialized, features: 0x%08x", - uring->uring_params.features); - - uring_task_init(&uring->task, "io_uring", &cfg->task, uring_free); - cfg->uring = uring; - - /* splice check, a bit convoluted, but seems to be no simpler way */ - cfg->splice_supported = false; - if (pipe2(uring->pipe, O_CLOEXEC) < 0) - die("pipe2: %m"); - uring->tfd = open("/dev/null", O_RDONLY | O_CLOEXEC | O_NOCTTY); - if (uring->tfd < 0) - die("open(\"/dev/null\"): %m"); - uring_splice(&uring->task, uring->tfd, uring->pipe[PIPE_WR], uring_splice_test_cb); -} - -static inline void -uring_print_cqe(const char *type, struct uring_task *task, - struct io_uring_cqe *cqe) -{ - assert_return(!empty_str(type) && task && cqe); - - debug(DBG_UR, "got CQE " - "(type: %s, res: %i (%s), task: %s (%p), fd: %i, cb: %p)", - type, - cqe->res, - cqe->res < 0 ? strerror(-cqe->res) : "ok", - task->name ? task->name : "", - task, - task->fd, - task->cb); -} - -void -uring_event_loop() -{ - while (true) { - struct io_uring_cqe *cqe; - unsigned nr, head; - int r; - - io_uring_submit(&cfg->uring->uring); - r = io_uring_wait_cqe(&cfg->uring->uring, &cqe); - if (r < 0) { - if (errno == EINTR) - continue; - else - die("io_uring_wait_cqe: %i", r); - } - - nr = 0; - io_uring_for_each_cqe(&cfg->uring->uring, head, cqe) { - struct uring_task *task = io_uring_cqe_get_data(cqe); - bool do_cb; - enum cqe_type cqe_type; - - cqe_count++; - - cqe_type = ((uintptr_t)task & CQE_TYPE_PTR_MASK); - task = (void *)((uintptr_t)task & ~CQE_TYPE_PTR_MASK); - - if (!task) - die("null task"); - - switch (cqe_type) { - case CQE_TYPE_CANCEL: - uring_print_cqe("cancel", task, cqe); - do_cb = false; - break; - - case CQE_TYPE_CLOSE: - uring_print_cqe("close", task, cqe); - do_cb = false; - break; - - case CQE_TYPE_POLL_CANCEL: - uring_print_cqe("poll_cancel", task, cqe); - do_cb = false; - break; - - case CQE_TYPE_NORMAL: - uring_print_cqe("standard", task, cqe); - do_cb = true; - break; - - default: - die("unknown CQE type"); - } - - if (do_cb && task->cb) - task->cb(task, cqe->res); - - uring_task_put(task); - - if (exiting) - return; - - nr++; - } - - io_uring_cq_advance(&cfg->uring->uring, nr); - } -} - diff --git a/uring.h b/uring.h deleted file mode 100644 index 9c33104..0000000 --- a/uring.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef foouringhfoo -#define foouringhfoo - -extern uint64_t sqe_count; -extern uint64_t cqe_count; - -void uring_task_refdump(struct uring_task *task); - -void uring_task_destroy(struct uring_task *task); - -void uring_task_put(struct uring_task *task); - -void uring_task_get(struct uring_task *task); - -void uring_task_set_buf(struct uring_task *task, struct uring_task_buf *tbuf); - -void uring_task_set_fd(struct uring_task *task, int fd); - -void uring_task_close_fd(struct uring_task *task); - -struct uring_task *uring_parent(); - -void uring_task_init(struct uring_task *task, const char *name, - struct uring_task *parent, - void (*free)(struct uring_task *)); - -void uring_close(struct uring_task *task, int fd); - -void uring_tbuf_write(struct uring_task *task, utask_cb_t final_cb); - -void uring_write(struct uring_task *task, void *buf, size_t len, utask_cb_t cb); - -void uring_tbuf_read_until(struct uring_task *task, - rutask_cb_t is_complete_cb, utask_cb_t final_cb); - -void uring_tbuf_read_until_eof(struct uring_task *task, utask_cb_t final_cb); - -void uring_tbuf_read(struct uring_task *task, utask_cb_t final_cb); - -void uring_read_offset(struct uring_task *task, void *buf, - size_t len, off_t offset, utask_cb_t cb); - -static inline void -uring_read(struct uring_task *task, void *buf, size_t len, utask_cb_t cb) -{ - uring_read_offset(task, buf, len, 0, cb); -} - -void uring_openat(struct uring_task *task, const char *path, utask_cb_t cb); - -void uring_tbuf_recvmsg(struct uring_task *task, utask_cb_t cb); - -void uring_tbuf_sendmsg(struct uring_task *task, utask_cb_t cb); - -void uring_connect(struct uring_task *task, struct saddr *saddr, utask_cb_t cb); - -void uring_accept(struct uring_task *task, struct saddr *saddr, utask_cb_t cb); - -void uring_splice(struct uring_task *task, int fd_in, int fd_out, utask_cb_t cb); - -void uring_poll(struct uring_task *task, short poll_mask, utask_cb_t cb); - -void uring_poll_cancel(struct uring_task *task); - -void uring_delete(); - -void uring_refdump(); - -void uring_init(); - -void uring_event_loop(); - -#endif diff --git a/utils.c b/utils.c deleted file mode 100644 index eacc586..0000000 --- a/utils.c +++ /dev/null @@ -1,430 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "main.h" -#include "utils.h" -#include "uring.h" - -static unsigned total_malloc_count = 0; -static int malloc_count = 0; - -LIST_HEAD(malloc_list); - -struct allocation { - const char *allocfn; - const char *callerfn; - int line; - void *ptr; - size_t size; - struct list_head list; -}; - -static void -add_allocation(const char *allocfn, const char *callerfn, int line, void *ptr, size_t size) -{ - struct allocation *a; - - assert_die(!empty_str(allocfn) && !empty_str(callerfn) && line > 0 && ptr && size > 0, "invalid arguments"); - - a = malloc(sizeof(*a)); - if (!a) - die("malloc: %m"); - a->allocfn = allocfn; - a->callerfn = callerfn; - a->line = line; - a->ptr = ptr; - a->size = size; - list_add(&a->list, &malloc_list); - total_malloc_count++; - malloc_count++; - debug(DBG_MALLOC, "called from %s:%i - %s(%zu) = %p (%p)", - callerfn, line, allocfn, size, ptr, a); -} - -void * -__zmalloc(const char *fn, int line, size_t size) -{ - void *ptr; - - assert_die(!empty_str(fn) && line > 0 && size > 0, "invalid arguments"); - - ptr = calloc(1, size); - if (ptr) - add_allocation("zmalloc", fn, line, ptr, size); - return ptr; -} - -char * -__xstrdup(const char *fn, int line, const char *s) -{ - char *ptr; - - assert_die(!empty_str(fn) && line > 0 && !empty_str(s), "invalid arguments"); - - ptr = strdup(s); - if (ptr) - add_allocation("xstrdup", fn, line, ptr, strlen(s) + 1); - return ptr; -} - -char * -__xstrndup(const char *fn, int line, const char *s, size_t n) -{ - char *ptr; - - assert_die(!empty_str(fn) && line > 0 && !empty_str(s) && n > 0, "invalid arguments"); - - ptr = strndup(s, n); - if (ptr) - add_allocation("xstrndup", fn, line, ptr, n); - return ptr; -} - -void -__xfree(const char *fn, int line, void *ptr) -{ - struct allocation *a, *tmp; - unsigned delete_count = 0; - - assert_die(!empty_str(fn) && line > 0, "invalid arguments"); - - if (!ptr) - return; - free(ptr); - malloc_count--; - - debug(DBG_MALLOC, "called from %s:%i - %p", fn, line, ptr); - - list_for_each_entry_safe(a, tmp, &malloc_list, list) { - if (a->ptr == ptr) { - list_del(&a->list); - free(a); - delete_count++; - } - } - - if (delete_count != 1) { - error("Delete count is %u for ptr 0x%p", delete_count, ptr); - exit(EXIT_FAILURE); - } -} - -void -debug_resource_usage() -{ - struct allocation *a; - DIR *dir; - struct dirent *dent; - char buf[4096]; - ssize_t r; - unsigned file_count = 0; - - debug(DBG_MALLOC, "Still malloced %i (total %u)", - malloc_count, total_malloc_count); - - list_for_each_entry(a, &malloc_list, list) { - debug(DBG_MALLOC, "* Lost allocation - %s:%i - ptr: %p, size: %zu", - a->callerfn, a->line, a->ptr, a->size); - } - - dir = opendir("/proc/self/fd"); - if (!dir) { - error("failed to open fd dir"); - return; - } - - debug(DBG_MALLOC, "Open files:"); - while ((dent = readdir(dir)) != NULL) { - if (streq(dent->d_name, ".") || streq(dent->d_name, "..")) - continue; - - r = readlinkat(dirfd(dir), dent->d_name, buf, sizeof(buf)); - if (r < 0) { - debug(DBG_MALLOC, "Failed to readlink %s", dent->d_name); - continue; - } - buf[r] = '\0'; - debug(DBG_MALLOC, " * %s -> %s", dent->d_name, buf); - file_count++; - } - closedir(dir); - - if (file_count > 4) - debug(DBG_MALLOC, "Lost file descriptor(s)"); - - debug(DBG_MALLOC, "CQEs used: %" PRIu64 ", SQEs used: %" PRIu64, - cqe_count, sqe_count); -} - -void -socket_set_low_latency(int sfd) -{ - int option; - - assert_return(sfd >= 0); - - /* Probably not necessary, but can't hurt */ - if (cfg->socket_defer) { - option = true; - if (setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, &option, sizeof(option)) < 0) - error("setsockopt: %m"); - } - - /* Doubtful if it has much effect, but can't hurt */ - if (cfg->socket_iptos) { - option = IPTOS_LOWDELAY; - if (setsockopt(sfd, IPPROTO_IP, IP_TOS, &option, sizeof(option)) < 0) - error("setsockopt: %m"); - } - - /* Nagle's algorithm is a poor fit for gaming */ - if (cfg->socket_nodelay) { - option = true; - if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &option, sizeof(option)) < 0) - error("setsockopt: %m"); - } -} - -void -connection_set_local(struct connection *conn, int fd) -{ - assert_return(conn && fd >= 0); - - conn->local.addrlen = sizeof(conn->local.storage); - if (getsockname(fd, (struct sockaddr *)&conn->local.storage, - &conn->local.addrlen) < 0) - sprintf(conn->local.addrstr, ""); - else - saddr_set_addrstr(&conn->local); -} - -void -connection_set_remote(struct connection *conn, struct saddr *remote) -{ - assert_return(conn && remote); - - conn->remote = *remote; - saddr_set_addrstr(&conn->remote); -} - -static void connect_next(struct uring_task *task, struct connection *conn); - -static void -connect_cb(struct uring_task *task, int res) -{ - struct connection *conn; - - assert_return(task && task->priv); - - conn = task->priv; - if (res < 0) { - debug(DBG_SRV, "%s: connection to %s failed", - task->name, conn->remote.addrstr); - uring_task_close_fd(task); - connect_next(task, conn); - return; - } - - connection_set_local(conn, task->fd); - - debug(DBG_SRV, "%s: connection established %s -> %s", - task->name, conn->local.addrstr, conn->remote.addrstr); - - conn->cb(conn, true); -} - -static void -connect_next(struct uring_task *task, struct connection *conn) -{ - struct saddr *remote, *tmp; - int sfd; - unsigned i; - - assert_return(task && conn && conn->cb); -again: - assert_task_alive_or(DBG_UR, task, goto out); - - i = 0; - remote = NULL; - list_for_each_entry(tmp, conn->addrs, list) { - if (i == conn->next_addr) { - remote = tmp; - break; - } - i++; - } - - if (!remote) { - debug(DBG_SRV, "%s: no more remote addresses to attempt", - task->name); - goto out; - } - - conn->next_addr++; - connection_set_remote(conn, remote); - debug(DBG_SRV, "%s: attempting to connect to %s", - task->name, conn->remote.addrstr); - - sfd = socket(conn->remote.storage.ss_family, SOCK_STREAM | SOCK_CLOEXEC, 0); - if (sfd < 0) { - error("socket: %m"); - goto again; - } - - socket_set_low_latency(sfd); - - task->priv = conn; - uring_task_set_fd(task, sfd); - uring_connect(task, &conn->remote, connect_cb); - return; - -out: - conn->cb(conn, false); -} - -void -connect_any(struct uring_task *task, - struct list_head *addrs, struct connection *conn, - connection_cb_t cb) -{ - assert_return(task && addrs && conn && cb); - - conn->next_addr = 0; - conn->addrs = addrs; - conn->cb = cb; - connect_next(task, conn); -} - -uint16_t -saddr_port(struct saddr *saddr) -{ - assert_return(saddr, 0); - - switch (saddr->storage.ss_family) { - case AF_INET: - return ntohs(saddr->in4.sin_port); - case AF_INET6: - return ntohs(saddr->in6.sin6_port); - default: - return 0; - } -} - -char * -saddr_addr(struct saddr *saddr, char *buf, size_t len) -{ - assert_return(saddr && buf && len > 0, NULL); - - switch (saddr->storage.ss_family) { - case AF_INET: - if (inet_ntop(saddr->in4.sin_family, &saddr->in4.sin_addr, buf, len)) - return buf; - break; - case AF_INET6: - if (inet_ntop(saddr->in6.sin6_family, &saddr->in6.sin6_addr, buf, len)) - return buf; - break; - default: - break; - } - - snprintf(buf, len, ""); - return buf; -} - -void -saddr_set_ipv4(struct saddr *saddr, in_addr_t ip, in_port_t port) -{ - assert_return(saddr); - - memset(&saddr->in4, 0, sizeof(saddr->in4)); - saddr->in4.sin_family = AF_INET; - saddr->in4.sin_port = port; - saddr->in4.sin_addr.s_addr = ip; - saddr->addrlen = sizeof(saddr->in4); - saddr_set_addrstr(saddr); -} - -void -saddr_set_ipv6(struct saddr *saddr, const struct in6_addr *ip, in_port_t port) -{ - assert_return(saddr && ip); - - memset(&saddr->in6, 0, sizeof(saddr->in6)); - saddr->in6.sin6_family = AF_INET6; - saddr->in6.sin6_port = port; - if (ip) - saddr->in6.sin6_addr = *ip; - saddr->addrlen = sizeof(saddr->in6); - saddr_set_addrstr(saddr); -} - -void -saddr_set_addrstr(struct saddr *saddr) -{ - assert_return(saddr); - - char abuf[ADDRSTRLEN]; - - switch (saddr->storage.ss_family) { - case AF_INET: - snprintf(saddr->addrstr, sizeof(saddr->addrstr), - "AF_INET4 %s %" PRIu16, - saddr_addr(saddr, abuf, sizeof(abuf)), - saddr_port(saddr)); - break; - case AF_INET6: - snprintf(saddr->addrstr, sizeof(saddr->addrstr), - "AF_INET6 %s %" PRIu16, - saddr_addr(saddr, abuf, sizeof(abuf)), - saddr_port(saddr)); - break; - default: - snprintf(saddr->addrstr, sizeof(saddr->addrstr), "AF_UNKNOWN"); - break; - } -} - -int -strtou16_strict(const char *str, uint16_t *result) -{ - char *end; - long val; - - assert_return(!empty_str(str) && result, -EINVAL); - - errno = 0; - val = strtol(str, &end, 10); - - if (errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) - return -EINVAL; - - if (errno != 0 && val == 0) - return -EINVAL; - - if (end == str) - return -EINVAL; - - if (*end != '\0') - return -EINVAL; - - if (val < 1 || val > UINT16_MAX) - return -EINVAL; - - if (result) - *result = val; - return 0; -} - diff --git a/utils.h b/utils.h deleted file mode 100644 index c36a36c..0000000 --- a/utils.h +++ /dev/null @@ -1,245 +0,0 @@ -#ifndef fooutilshfoo -#define fooutilshfoo - -#include -#include -#include -#include -#include - -struct list_head { - struct list_head *next; - struct list_head *prev; -}; - -#define zmalloc(s) __zmalloc(__func__, __LINE__, s) -void *__zmalloc(const char *fn, int line, size_t s); - -#define xstrdup(s) __xstrdup(__func__, __LINE__, s) -char *__xstrdup(const char *fn, int line, const char *s); - -#define xstrndup(s, n) __xstrndup(__func__, __LINE__, s, n) -char *__xstrndup(const char *fn, int line, const char *s, size_t n); - -#define xfree(s) __xfree(__func__, __LINE__, s) -void __xfree(const char *fn, int line, void *ptr); - -void debug_resource_usage(); - -/* Length of longest DNS name = 253 + trailing dot */ -#define FQDN_STR_LEN 254 - -/* Length of longest port string = strlen("65535") */ -#define PORT_STR_LEN 5 - -/* Length of longest address family string = strlen("AF_INETX") */ -#define AF_STR_LEN 8 - -/* Length of longest addrstr, format = "AF_INETX */ -#define ADDRSTRLEN (AF_STR_LEN + 1 + INET6_ADDRSTRLEN + 1 + PORT_STR_LEN + 1) -struct saddr { - union { - struct sockaddr_storage storage; - struct sockaddr_in in4; - struct sockaddr_in6 in6; - struct sockaddr_ll ll; - }; - socklen_t addrlen; - char addrstr[ADDRSTRLEN]; - struct list_head list; -}; - -struct connection; - -typedef void(*connection_cb_t)(struct connection *, bool); - -struct connection { - struct saddr remote; - struct saddr local; - - struct list_head *addrs; - unsigned next_addr; - - connection_cb_t cb; -}; - -struct uring_task; - -void socket_set_low_latency(int sfd); - -void connection_set_local(struct connection *conn, int fd); - -void connection_set_remote(struct connection *conn, struct saddr *remote); - -void connect_any(struct uring_task *task, - struct list_head *addrs, struct connection *conn, - connection_cb_t cb); - -char *saddr_addr(struct saddr *saddr, char *buf, size_t len); - -uint16_t saddr_port(struct saddr *saddr); - -void saddr_set_ipv4(struct saddr *saddr, in_addr_t ip, in_port_t port); - -void saddr_set_ipv6(struct saddr *saddr, const struct in6_addr *ip, in_port_t port); - -void saddr_set_addrstr(struct saddr *saddr); - -int strtou16_strict(const char *str, uint16_t *result); - -static inline bool empty_str(const char *str) -{ - if (!str || str[0] == '\0') - return true; - else - return false; -} - -static inline bool streq(const char *a, const char *b) -{ - return strcmp(a, b) == 0; -} - -static inline bool strcaseeq(const char *a, const char *b) -{ - return strcasecmp(a, b) == 0; -} - -#if __BYTE_ORDER == __LITTLE_ENDIAN -#define chtobe32(x) __bswap_constant_32(x) -#else -#define chtobe32(x) (x) -#endif - -#if __BYTE_ORDER == __LITTLE_ENDIAN -#define cinet_addr(a,b,c,d) ((uint32_t)((a)<<0|(b)<<8|(c)<<16|(d)<<24)) -#else -#define cinet_addr(a,b,c,d) ((uint32_t)((a)<<24|(b)<<16|(c)<<8|(d)<<0)) -#endif - -#define LIST_HEAD_INIT(name) { &(name), &(name) } - -#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) - -static inline void list_init(struct list_head *list) -{ - list->next = list; - list->prev = list; -} - -static inline void list_del(struct list_head *list) -{ - list->next->prev = list->prev; - list->prev->next = list->next; -} - -static inline void list_add(struct list_head *new, struct list_head *list) -{ - list->next->prev = new; - new->next = list->next; - new->prev = list; - list->next = new; -} - -static inline void list_replace(struct list_head *old, struct list_head *new) -{ - old->prev->next = new; - old->next->prev = new; - new->next = old->next; - new->prev = old->prev; - old->next = old->prev = NULL; -} - -static inline bool list_empty(struct list_head *list) -{ - return list->next == list; -} - -#define PIPE_RD 0 -#define PIPE_WR 1 - -#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) - -#define MAX(a, b) ((a) > (b) ? (a) : (b)) - -#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) - -#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) - -#define container_of(ptr, type, member) ({ \ - const typeof( ((type *)0)->member ) *__mptr = (ptr); \ - (type *)( (char *)__mptr - offsetof(type,member) );}) - -#define list_entry(ptr, type, member) \ - container_of(ptr, type, member) - -#define list_first_entry(ptr, type, member) \ - list_entry((ptr)->next, type, member) - -#define list_next_entry(pos, member) \ - list_entry((pos)->member.next, typeof(*(pos)), member) - -#define list_for_each(pos, head) \ - for (pos = (head)->next; pos != (head); pos = pos->next) - -#define list_for_each_entry(pos, head, member) \ - for (pos = list_first_entry(head, typeof(*pos), member); \ - &pos->member != (head); \ - pos = list_next_entry(pos, member)) - -#define list_for_each_entry_safe(pos, n, head, member) \ - for (pos = list_entry((head)->next, typeof(*pos), member), \ - n = list_entry(pos->member.next, typeof(*pos), member); \ - &pos->member != (head); \ - pos = n, n = list_entry(n->member.next, typeof(*n), member)) - -/* -#define _cleanup_(x) __attribute__((cleanup(x))) - -#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ - static inline void func##p(type *p) { \ - if (*p) \ - func(*p); \ - } \ - struct __useless_struct_to_allow_trailing_semicolon__ - -static inline unsigned -strv_length(char **strv) -{ - unsigned len; - - for (len = 0; strv && *strv; strv++) - len++; - - return len; -} - -static inline void strv_free(char **l) { - char **k; - if (l) { - for (k = l; *k; k++) - free(k); - free(l); - } -} -DEFINE_TRIVIAL_CLEANUP_FUNC(char **, strv_free); -#define _cleanup_strv_free_ _cleanup_(strv_freep) - -static inline void freep(void *p) { - free(*(void**) p); -} -#define _cleanup_free_ _cleanup_(freep) - -DEFINE_TRIVIAL_CLEANUP_FUNC(int, close); -#define _cleanup_close_ _cleanup_(closep) - -DEFINE_TRIVIAL_CLEANUP_FUNC(FILE *, fclose); -#define _cleanup_fclose_ _cleanup_(fclosep) - -DEFINE_TRIVIAL_CLEANUP_FUNC(DIR *, closedir); -#define _cleanup_closedir_ _cleanup_(closedirp) - -*/ - -#endif - -- cgit v1.2.3