#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; }