From ea053d96f7e89e053d4af8d39b04c5428760345f Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Tue, 23 Jun 2020 20:56:22 +0200 Subject: Big renaming, move some more functionality to shared lib --- 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 | 6 +- minecctl/meson.build | 14 + minecctl/minecctl.c | 40 ++ minecproxy/announce.c | 116 ++++++ minecproxy/announce.h | 14 + minecproxy/config-parser.c | 490 ++++++++++++++++++++++++ minecproxy/config-parser.h | 59 +++ minecproxy/idle.c | 378 +++++++++++++++++++ minecproxy/idle.h | 13 + minecproxy/igmp.c | 587 +++++++++++++++++++++++++++++ minecproxy/igmp.h | 10 + minecproxy/main.c | 741 ++++++++++++++++++++++++++++++++++++ minecproxy/main.h | 107 ++++++ minecproxy/meson.build | 36 ++ minecproxy/misc.c | 281 ++++++++++++++ minecproxy/misc.h | 36 ++ minecproxy/ptimer.c | 223 +++++++++++ minecproxy/ptimer.h | 33 ++ minecproxy/server-config.c | 580 ++++++++++++++++++++++++++++ minecproxy/server-config.h | 10 + minecproxy/server-proxy.c | 578 ++++++++++++++++++++++++++++ minecproxy/server-proxy.h | 51 +++ minecproxy/server-rcon.c | 227 +++++++++++ minecproxy/server-rcon.h | 12 + minecproxy/server.c | 836 ++++++++++++++++++++++++++++++++++++++++ minecproxy/server.h | 128 +++++++ minecproxy/signal-handler.c | 195 ++++++++++ minecproxy/signal-handler.h | 8 + minecproxy/systemd.c | 219 +++++++++++ minecproxy/systemd.h | 14 + minecproxy/uring.c | 759 +++++++++++++++++++++++++++++++++++++ minecproxy/uring.h | 73 ++++ shared/debug.h | 94 +++++ shared/list.h | 77 ++++ shared/meson.build | 1 + shared/utils.c | 169 +++++++++ shared/utils.h | 106 ++++++ 68 files changed, 7318 insertions(+), 7297 deletions(-) delete mode 100644 mcserverctl/mcserverctl.c delete mode 100644 mcserverctl/meson.build delete mode 100644 mcserverproxy/announce.c delete mode 100644 mcserverproxy/announce.h delete mode 100644 mcserverproxy/config-parser.c delete mode 100644 mcserverproxy/config-parser.h delete mode 100644 mcserverproxy/idle.c delete mode 100644 mcserverproxy/idle.h delete mode 100644 mcserverproxy/igmp.c delete mode 100644 mcserverproxy/igmp.h delete mode 100644 mcserverproxy/main.c delete mode 100644 mcserverproxy/main.h delete mode 100644 mcserverproxy/meson.build delete mode 100644 mcserverproxy/ptimer.c delete mode 100644 mcserverproxy/ptimer.h delete mode 100644 mcserverproxy/server-config.c delete mode 100644 mcserverproxy/server-config.h delete mode 100644 mcserverproxy/server-proxy.c delete mode 100644 mcserverproxy/server-proxy.h delete mode 100644 mcserverproxy/server-rcon.c delete mode 100644 mcserverproxy/server-rcon.h delete mode 100644 mcserverproxy/server.c delete mode 100644 mcserverproxy/server.h delete mode 100644 mcserverproxy/signal-handler.c delete mode 100644 mcserverproxy/signal-handler.h delete mode 100644 mcserverproxy/systemd.c delete mode 100644 mcserverproxy/systemd.h delete mode 100644 mcserverproxy/uring.c delete mode 100644 mcserverproxy/uring.h delete mode 100644 mcserverproxy/utils.c delete mode 100644 mcserverproxy/utils.h create mode 100644 minecctl/meson.build create mode 100644 minecctl/minecctl.c create mode 100644 minecproxy/announce.c create mode 100644 minecproxy/announce.h create mode 100644 minecproxy/config-parser.c create mode 100644 minecproxy/config-parser.h create mode 100644 minecproxy/idle.c create mode 100644 minecproxy/idle.h create mode 100644 minecproxy/igmp.c create mode 100644 minecproxy/igmp.h create mode 100644 minecproxy/main.c create mode 100644 minecproxy/main.h create mode 100644 minecproxy/meson.build create mode 100644 minecproxy/misc.c create mode 100644 minecproxy/misc.h create mode 100644 minecproxy/ptimer.c create mode 100644 minecproxy/ptimer.h create mode 100644 minecproxy/server-config.c create mode 100644 minecproxy/server-config.h create mode 100644 minecproxy/server-proxy.c create mode 100644 minecproxy/server-proxy.h create mode 100644 minecproxy/server-rcon.c create mode 100644 minecproxy/server-rcon.h create mode 100644 minecproxy/server.c create mode 100644 minecproxy/server.h create mode 100644 minecproxy/signal-handler.c create mode 100644 minecproxy/signal-handler.h create mode 100644 minecproxy/systemd.c create mode 100644 minecproxy/systemd.h create mode 100644 minecproxy/uring.c create mode 100644 minecproxy/uring.h create mode 100644 shared/debug.h create mode 100644 shared/list.h create mode 100644 shared/utils.c create mode 100644 shared/utils.h diff --git a/mcserverctl/mcserverctl.c b/mcserverctl/mcserverctl.c deleted file mode 100644 index e29dcef..0000000 --- a/mcserverctl/mcserverctl.c +++ /dev/null @@ -1,40 +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/meson.build b/mcserverctl/meson.build deleted file mode 100644 index 4e960b7..0000000 --- a/mcserverctl/meson.build +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 13ef423..0000000 --- a/mcserverproxy/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/mcserverproxy/announce.h b/mcserverproxy/announce.h deleted file mode 100644 index 77a36f2..0000000 --- a/mcserverproxy/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/mcserverproxy/config-parser.c b/mcserverproxy/config-parser.c deleted file mode 100644 index ffed7f1..0000000 --- a/mcserverproxy/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/mcserverproxy/config-parser.h b/mcserverproxy/config-parser.h deleted file mode 100644 index 3a117a3..0000000 --- a/mcserverproxy/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/mcserverproxy/idle.c b/mcserverproxy/idle.c deleted file mode 100644 index c49846d..0000000 --- a/mcserverproxy/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/mcserverproxy/idle.h b/mcserverproxy/idle.h deleted file mode 100644 index d7e4ab0..0000000 --- a/mcserverproxy/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/mcserverproxy/igmp.c b/mcserverproxy/igmp.c deleted file mode 100644 index dc43a9f..0000000 --- a/mcserverproxy/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/mcserverproxy/igmp.h b/mcserverproxy/igmp.h deleted file mode 100644 index 80875b0..0000000 --- a/mcserverproxy/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/mcserverproxy/main.c b/mcserverproxy/main.c deleted file mode 100644 index 047e70e..0000000 --- a/mcserverproxy/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/mcserverproxy/main.h b/mcserverproxy/main.h deleted file mode 100644 index 256ddae..0000000 --- a/mcserverproxy/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/mcserverproxy/meson.build b/mcserverproxy/meson.build deleted file mode 100644 index e5fa7bc..0000000 --- a/mcserverproxy/meson.build +++ /dev/null @@ -1,36 +0,0 @@ -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 deleted file mode 100644 index 5f9cf5d..0000000 --- a/mcserverproxy/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/mcserverproxy/ptimer.h b/mcserverproxy/ptimer.h deleted file mode 100644 index 0b53590..0000000 --- a/mcserverproxy/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/mcserverproxy/server-config.c b/mcserverproxy/server-config.c deleted file mode 100644 index 549cf16..0000000 --- a/mcserverproxy/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/mcserverproxy/server-config.h b/mcserverproxy/server-config.h deleted file mode 100644 index 590dae0..0000000 --- a/mcserverproxy/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/mcserverproxy/server-proxy.c b/mcserverproxy/server-proxy.c deleted file mode 100644 index 4cbbb87..0000000 --- a/mcserverproxy/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/mcserverproxy/server-proxy.h b/mcserverproxy/server-proxy.h deleted file mode 100644 index ee3bda3..0000000 --- a/mcserverproxy/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/mcserverproxy/server-rcon.c b/mcserverproxy/server-rcon.c deleted file mode 100644 index 1f8ef70..0000000 --- a/mcserverproxy/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/mcserverproxy/server-rcon.h b/mcserverproxy/server-rcon.h deleted file mode 100644 index 6625f25..0000000 --- a/mcserverproxy/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/mcserverproxy/server.c b/mcserverproxy/server.c deleted file mode 100644 index de42721..0000000 --- a/mcserverproxy/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/mcserverproxy/server.h b/mcserverproxy/server.h deleted file mode 100644 index ff4c28e..0000000 --- a/mcserverproxy/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/mcserverproxy/signal-handler.c b/mcserverproxy/signal-handler.c deleted file mode 100644 index 67c2e0b..0000000 --- a/mcserverproxy/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/mcserverproxy/signal-handler.h b/mcserverproxy/signal-handler.h deleted file mode 100644 index e0140b3..0000000 --- a/mcserverproxy/signal-handler.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef foosignalhfoo -#define foosignalhfoo - -void signal_refdump(); - -void signal_init(); - -#endif diff --git a/mcserverproxy/systemd.c b/mcserverproxy/systemd.c deleted file mode 100644 index a44b0d8..0000000 --- a/mcserverproxy/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/mcserverproxy/systemd.h b/mcserverproxy/systemd.h deleted file mode 100644 index d455044..0000000 --- a/mcserverproxy/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/mcserverproxy/uring.c b/mcserverproxy/uring.c deleted file mode 100644 index e979471..0000000 --- a/mcserverproxy/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/mcserverproxy/uring.h b/mcserverproxy/uring.h deleted file mode 100644 index 9c33104..0000000 --- a/mcserverproxy/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/mcserverproxy/utils.c b/mcserverproxy/utils.c deleted file mode 100644 index eacc586..0000000 --- a/mcserverproxy/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/mcserverproxy/utils.h b/mcserverproxy/utils.h deleted file mode 100644 index c36a36c..0000000 --- a/mcserverproxy/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 - diff --git a/meson.build b/meson.build index 1f8424a..cc9edc1 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('mcproxy', +project('minecproxy', 'c', version: '0.1.0', license: 'GPL2+', @@ -29,6 +29,6 @@ dep_config_h = declare_dependency( ) subdir('shared') -subdir('mcserverproxy') -subdir('mcserverctl') +subdir('minecproxy') +subdir('minecctl') diff --git a/minecctl/meson.build b/minecctl/meson.build new file mode 100644 index 0000000..3490338 --- /dev/null +++ b/minecctl/meson.build @@ -0,0 +1,14 @@ +minecctl_sources = [ + 'minecctl.c', +] + +minecctl_deps = [ + dep_libshared, +] + +executable( + 'minecctl', + minecctl_sources, + dependencies: minecctl_deps, +) + diff --git a/minecctl/minecctl.c b/minecctl/minecctl.c new file mode 100644 index 0000000..e29dcef --- /dev/null +++ b/minecctl/minecctl.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/minecproxy/announce.c b/minecproxy/announce.c new file mode 100644 index 0000000..13ef423 --- /dev/null +++ b/minecproxy/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/minecproxy/announce.h b/minecproxy/announce.h new file mode 100644 index 0000000..77a36f2 --- /dev/null +++ b/minecproxy/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/minecproxy/config-parser.c b/minecproxy/config-parser.c new file mode 100644 index 0000000..9c89cf2 --- /dev/null +++ b/minecproxy/config-parser.c @@ -0,0 +1,490 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.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/minecproxy/config-parser.h b/minecproxy/config-parser.h new file mode 100644 index 0000000..3a117a3 --- /dev/null +++ b/minecproxy/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/minecproxy/idle.c b/minecproxy/idle.c new file mode 100644 index 0000000..c49846d --- /dev/null +++ b/minecproxy/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/minecproxy/idle.h b/minecproxy/idle.h new file mode 100644 index 0000000..d7e4ab0 --- /dev/null +++ b/minecproxy/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/minecproxy/igmp.c b/minecproxy/igmp.c new file mode 100644 index 0000000..dc43a9f --- /dev/null +++ b/minecproxy/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/minecproxy/igmp.h b/minecproxy/igmp.h new file mode 100644 index 0000000..80875b0 --- /dev/null +++ b/minecproxy/igmp.h @@ -0,0 +1,10 @@ +#ifndef fooigmphfoo +#define fooigmphfoo + +void igmp_refdump(); + +void igmp_delete(); + +void igmp_init(); + +#endif diff --git a/minecproxy/main.c b/minecproxy/main.c new file mode 100644 index 0000000..bbe3fad --- /dev/null +++ b/minecproxy/main.c @@ -0,0 +1,741 @@ +#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; + +/* 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; + + debug_mask = DBG_ERROR | DBG_INFO; + + 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/minecproxy/main.h b/minecproxy/main.h new file mode 100644 index 0000000..f1f5df2 --- /dev/null +++ b/minecproxy/main.h @@ -0,0 +1,107 @@ +#ifndef foomainhfoo +#define foomainhfoo + +#include +#include + +struct cfg; +struct uring_task; + +#include "misc.h" +#include "utils.h" + +extern struct cfg *cfg; +extern bool exiting; + +/* +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), +}; +*/ + +void dump_tree(); + +/* 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/minecproxy/meson.build b/minecproxy/meson.build new file mode 100644 index 0000000..db6a31b --- /dev/null +++ b/minecproxy/meson.build @@ -0,0 +1,36 @@ +minecproxy_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', + 'misc.c', +] + +dep_liburing = dependency('liburing') +dep_libsystemd = dependency('libsystemd') +dep_libcapng = dependency('libcap-ng') + +minecproxy_deps = [ + dep_liburing, + dep_libsystemd, + dep_libcapng, + dep_config_h, + dep_libshared, +] + +executable( + 'minecproxy', + minecproxy_sources, + link_args: [ '-lanl' ], + dependencies: minecproxy_deps, +) + diff --git a/minecproxy/misc.c b/minecproxy/misc.c new file mode 100644 index 0000000..f954618 --- /dev/null +++ b/minecproxy/misc.c @@ -0,0 +1,281 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "misc.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 +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, cfg->socket_keepalive, + cfg->socket_iptos, cfg->socket_nodelay); + + 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); +} + diff --git a/minecproxy/misc.h b/minecproxy/misc.h new file mode 100644 index 0000000..6627913 --- /dev/null +++ b/minecproxy/misc.h @@ -0,0 +1,36 @@ +#ifndef foomischfoo +#define foomischfoo + +#include +#include +#include +#include +#include + +#include "utils.h" + +void debug_resource_usage(); + +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; +}; + +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); + +#endif diff --git a/minecproxy/ptimer.c b/minecproxy/ptimer.c new file mode 100644 index 0000000..5f9cf5d --- /dev/null +++ b/minecproxy/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/minecproxy/ptimer.h b/minecproxy/ptimer.h new file mode 100644 index 0000000..0b53590 --- /dev/null +++ b/minecproxy/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/minecproxy/server-config.c b/minecproxy/server-config.c new file mode 100644 index 0000000..549cf16 --- /dev/null +++ b/minecproxy/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/minecproxy/server-config.h b/minecproxy/server-config.h new file mode 100644 index 0000000..590dae0 --- /dev/null +++ b/minecproxy/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/minecproxy/server-proxy.c b/minecproxy/server-proxy.c new file mode 100644 index 0000000..d8ff0cf --- /dev/null +++ b/minecproxy/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" + +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, cfg->socket_keepalive, + cfg->socket_iptos, cfg->socket_nodelay); + + 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/minecproxy/server-proxy.h b/minecproxy/server-proxy.h new file mode 100644 index 0000000..ee3bda3 --- /dev/null +++ b/minecproxy/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/minecproxy/server-rcon.c b/minecproxy/server-rcon.c new file mode 100644 index 0000000..1f8ef70 --- /dev/null +++ b/minecproxy/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/minecproxy/server-rcon.h b/minecproxy/server-rcon.h new file mode 100644 index 0000000..6625f25 --- /dev/null +++ b/minecproxy/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/minecproxy/server.c b/minecproxy/server.c new file mode 100644 index 0000000..534ceca --- /dev/null +++ b/minecproxy/server.c @@ -0,0 +1,836 @@ +#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 "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/minecproxy/server.h b/minecproxy/server.h new file mode 100644 index 0000000..ff4c28e --- /dev/null +++ b/minecproxy/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/minecproxy/signal-handler.c b/minecproxy/signal-handler.c new file mode 100644 index 0000000..67c2e0b --- /dev/null +++ b/minecproxy/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/minecproxy/signal-handler.h b/minecproxy/signal-handler.h new file mode 100644 index 0000000..e0140b3 --- /dev/null +++ b/minecproxy/signal-handler.h @@ -0,0 +1,8 @@ +#ifndef foosignalhfoo +#define foosignalhfoo + +void signal_refdump(); + +void signal_init(); + +#endif diff --git a/minecproxy/systemd.c b/minecproxy/systemd.c new file mode 100644 index 0000000..a44b0d8 --- /dev/null +++ b/minecproxy/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/minecproxy/systemd.h b/minecproxy/systemd.h new file mode 100644 index 0000000..d455044 --- /dev/null +++ b/minecproxy/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/minecproxy/uring.c b/minecproxy/uring.c new file mode 100644 index 0000000..e979471 --- /dev/null +++ b/minecproxy/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/minecproxy/uring.h b/minecproxy/uring.h new file mode 100644 index 0000000..9c33104 --- /dev/null +++ b/minecproxy/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/shared/debug.h b/shared/debug.h new file mode 100644 index 0000000..8581a9a --- /dev/null +++ b/shared/debug.h @@ -0,0 +1,94 @@ +#ifndef foodebughfoo +#define foodebughfoo + +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); +} + +/* These need to be defined in the linking binary */ +void __debug(enum debug_lvl lvl, const char *fmt, ...) __attribute__((format(printf, 2, 3))); + +void __die(const char *fmt, ...) __attribute__((format(printf, 1, 2))); + +#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__) + +#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) + +#endif diff --git a/shared/list.h b/shared/list.h new file mode 100644 index 0000000..df4092a --- /dev/null +++ b/shared/list.h @@ -0,0 +1,77 @@ +#ifndef foolisthfoo +#define foolisthfoo + +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +#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 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)) + +#endif + diff --git a/shared/meson.build b/shared/meson.build index cbb7dca..ccf502e 100644 --- a/shared/meson.build +++ b/shared/meson.build @@ -1,5 +1,6 @@ srcs_libshared = [ 'rcon-protocol.c', + 'utils.c', ] inc_libshared = include_directories('.') diff --git a/shared/utils.c b/shared/utils.c new file mode 100644 index 0000000..1284a73 --- /dev/null +++ b/shared/utils.c @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +unsigned debug_mask = 0; + +void +socket_set_low_latency(int sfd, bool keepalive, bool iptos, bool nodelay) +{ + int option; + + assert_return(sfd >= 0); + + /* Probably not necessary, but can't hurt */ + if (keepalive) { + 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 (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 (nodelay) { + option = true; + if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &option, sizeof(option)) < 0) + error("setsockopt: %m"); + } +} + +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/shared/utils.h b/shared/utils.h new file mode 100644 index 0000000..df728b2 --- /dev/null +++ b/shared/utils.h @@ -0,0 +1,106 @@ +#ifndef fooutilshfoo +#define fooutilshfoo + +#include +#include +#include +#include +#include + +extern unsigned debug_mask; + +#include "list.h" +#include "debug.h" + +/* These functions need to be defined in the linking binary */ +#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); + +/* 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; +}; + +void socket_set_low_latency(int sfd, bool keepalive, bool iptos, bool nodelay); + +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 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)) + +#endif + -- cgit v1.2.3