From dc93ade1436f619a90a9eee7d98ff91ecaccb6ab Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Wed, 1 Jul 2020 16:50:45 +0200 Subject: Return some sanity to DNS handling --- shared/config-parser.c | 357 +++++++++++++++++++++---------------------------- 1 file changed, 153 insertions(+), 204 deletions(-) (limited to 'shared/config-parser.c') diff --git a/shared/config-parser.c b/shared/config-parser.c index 878262e..b8abecb 100644 --- a/shared/config-parser.c +++ b/shared/config-parser.c @@ -14,61 +14,25 @@ #include "server-config-options.h" #include "config.h" -static void handle_dns_reply(struct server_config *scfg, enum scfg_keys type, - struct saddr *saddr) +static bool handle_addrinfo_results(struct addrinfo *results, + struct list_head *target_list, + unsigned *naddrs) { - switch (type) { - case SCFG_KEY_LOCAL: - list_add(&saddr->list, &scfg->locals); - break; - case SCFG_KEY_REMOTE: - list_add(&saddr->list, &scfg->remotes); - break; - case SCFG_KEY_RCON: - list_add(&saddr->list, &scfg->rcons); - break; - default: - error("invalid DNS reply type"); - break; - } -} - -static void scfg_dns_cb(struct dns_async *dns, enum scfg_keys type) -{ - struct server_config *scfg; struct sockaddr_in *in4; struct sockaddr_in6 *in6; struct saddr *saddr; - struct addrinfo *results = NULL, *ai; - int r; - - assert_return(dns); - - scfg = dns->priv; - debug(DBG_DNS, "called, dns: %p, name: %s", dns, dns->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; + struct addrinfo *ai; + bool rv = true; for (ai = results; ai; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && + ai->ai_family != AF_INET6) + continue; + saddr = zmalloc(sizeof(*saddr)); if (!saddr) { - error("DNS lookup of %s:%s failed: %m", dns->name, - dns->port); - goto out; + error("DNS lookup failed: %m"); + rv = false; } switch (ai->ai_family) { @@ -76,105 +40,78 @@ static void scfg_dns_cb(struct dns_async *dns, enum scfg_keys type) in4 = (struct sockaddr_in *)ai->ai_addr; saddr_set_ipv4(saddr, in4->sin_addr.s_addr, in4->sin_port); + (*naddrs)++; break; case AF_INET6: in6 = (struct sockaddr_in6 *)ai->ai_addr; saddr_set_ipv6(saddr, &in6->sin6_addr, in6->sin6_port); break; - - default: - error("getaddrinfo(%s:%s): unknown address family (%i)", - dns->name, dns->port, ai->ai_family); - xfree(saddr); - continue; + (*naddrs)++; } - handle_dns_reply(scfg, type, saddr); + list_add(&saddr->list, target_list); } -out: freeaddrinfo(results); - list_del(&dns->list); - if (dns->notification_cb) - dns->notification_cb(scfg, true); - xfree(dns); -} - -static void scfg_dns_local_cb(struct dns_async *dns) -{ - scfg_dns_cb(dns, SCFG_KEY_LOCAL); -} - -static void scfg_dns_remote_cb(struct dns_async *dns) -{ - scfg_dns_cb(dns, SCFG_KEY_REMOTE); + return rv; } -static void scfg_dns_rcon_cb(struct dns_async *dns) +void scfg_async_dns_result(struct server_config *scfg) { - scfg_dns_cb(dns, SCFG_KEY_RCON); -} - -/* - * FIXME: this is brittle, the dns request has already been triggered, - * can't just cancel on error. - */ -static bool handle_dns(struct server_config *scfg, enum scfg_keys type, - struct cfg_value *value, - notification_cb_t notification_cb) -{ - struct saddr *saddr, *tmp; - struct dns_async *dns; - - assert_return(scfg && type && value, false); + struct dns_async *dns, *tmp; + unsigned naddrs = 0; + bool alldone = true; + int r; - switch (value->type) { - case CFG_VAL_TYPE_ADDRS: - debug(DBG_DNS, "%i: got immediate addrs", type); + assert_return(scfg); - list_for_each_entry_safe(saddr, tmp, &value->saddrs, list) { - list_del(&saddr->list); - handle_dns_reply(scfg, type, saddr); - } - return true; + debug(DBG_DNS, "called, scfg: %p", scfg); - case CFG_VAL_TYPE_ASYNC_ADDRS: - debug(DBG_DNS, "doing async lookup of DNS record: %p", - value->dns_async); + list_for_each_entry(dns, &scfg->dnslookups, list) { + if (!dns->pending) + continue; - dns = value->dns_async; - switch (type) { - case SCFG_KEY_LOCAL: - dns->cb = scfg_dns_local_cb; + r = gai_error(&dns->gcb); + switch (r) { + case EAI_INPROGRESS: + /* Assume we'll get called again */ + alldone = false; break; - case SCFG_KEY_REMOTE: - dns->cb = scfg_dns_remote_cb; + + case EAI_CANCELED: + /* The server must be in the process of going away */ + dns->pending = false; break; - case SCFG_KEY_RCON: - dns->cb = scfg_dns_rcon_cb; + + case 0: + /* Success */ + dns->pending = false; + handle_addrinfo_results(dns->gcb.ar_result, + dns->target_list, + &naddrs); + debug(DBG_DNS, "async DNS added %u records", naddrs); break; - default: - error("invalid DNS lookup type"); - /* Note: we'll leak dns here, but not much we can do */ - return false; - } - if (!notification_cb) { - error("async DNS lookup received but not requested"); - /* Ditto */ - return false; + default: + /* Misc failures */ + dns->pending = false; + error("DNS lookup of %s:%s failed: %s", + dns->name, dns->port, gai_strerror(r)); + break; } + } - dns->priv = scfg; - dns->notification_cb = notification_cb; - list_add(&dns->list, &scfg->dnslookups); - dns->notification_cb(scfg, false); - return true; + if (!alldone) + return; - default: - return false; + list_for_each_entry_safe(dns, tmp, &scfg->dnslookups, list) { + list_del(&dns->list); + xfree(dns); } + + xfree(scfg->gcbs); + scfg->gcbs = NULL; } static inline char tohex(uint8_t val) @@ -318,9 +255,69 @@ bool scfg_validate(struct server_config *scfg, const char **error) return true; } -bool scfg_parse(struct server_config *scfg, char *buf, - notification_cb_t notification_cb, unsigned *lineno, - const char **error) +bool scfg_async_dns_start(struct server_config *scfg) +{ + struct dns_async *dns; + unsigned ndns = 0; + int r; + + assert_return(scfg, false); + + list_for_each_entry(dns, &scfg->dnslookups, list) + ndns++; + + scfg->gcbs = zmalloc(sizeof(struct gaicb) * ndns); + if (!scfg->gcbs) { + error("failed to allocate gcbs: %m"); + return false; + } + + ndns = 0; + list_for_each_entry(dns, &scfg->dnslookups, list) { + scfg->gcbs[ndns++] = &dns->gcb; + dns->pending = true; + } + + r = getaddrinfo_a(GAI_NOWAIT, scfg->gcbs, ndns, &scfg->dns_sev); + if (r != 0) { + error("getaddrinfo_a(%u): %s", ndns, gai_strerror(r)); + /* FIXME: More error handling */ + } + + return true; +} + +static void scfg_handle_dns(struct server_config *scfg, struct cfg_value *value, + struct list_head *target_list) +{ + struct saddr *saddr, *tmp; + struct dns_async *dns; + + switch (value->type) { + case CFG_VAL_TYPE_ADDRS: + debug(DBG_DNS, "got sync results"); + list_for_each_entry_safe(saddr, tmp, &value->saddrs, list) { + list_del(&saddr->list); + list_add(&saddr->list, target_list); + } + break; + + case CFG_VAL_TYPE_ASYNC_ADDRS: + dns = value->dns_async; + debug(DBG_DNS, "enqueing async lookup of %s:%s (%p)", + dns->name, dns->port, dns); + dns->target_list = target_list; + list_add(&dns->list, &scfg->dnslookups); + break; + + default: + error("Unexpected value type"); + break; + } +} + +bool scfg_parse(struct server_config *scfg, char *buf, bool async, + unsigned *lineno, const char **error) { char *pos; @@ -339,7 +336,7 @@ bool scfg_parse(struct server_config *scfg, char *buf, struct cfg_value value; if (!config_parse_line(scfg->filename, &pos, scfg_key_map, &key, - &keyname, &value, !!notification_cb, lineno)) + &keyname, &value, async, lineno)) break; if (key == SCFG_KEY_INVALID) @@ -376,15 +373,11 @@ bool scfg_parse(struct server_config *scfg, char *buf, break; case SCFG_KEY_LOCAL: - if (!handle_dns(scfg, SCFG_KEY_LOCAL, &value, - notification_cb)) - ERROR("handle_dns failed"); + scfg_handle_dns(scfg, &value, &scfg->locals); break; case SCFG_KEY_REMOTE: - if (!handle_dns(scfg, SCFG_KEY_REMOTE, &value, - notification_cb)) - ERROR("handle_dns failed"); + scfg_handle_dns(scfg, &value, &scfg->remotes); break; case SCFG_KEY_IDLE_TIMEOUT: @@ -433,9 +426,7 @@ bool scfg_parse(struct server_config *scfg, char *buf, break; case SCFG_KEY_RCON: - if (!handle_dns(scfg, SCFG_KEY_RCON, &value, - notification_cb)) - ERROR("handle_dns failed"); + scfg_handle_dns(scfg, &value, &scfg->rcons); break; case SCFG_KEY_RCON_PASSWORD: @@ -514,8 +505,10 @@ void scfg_delete(struct server_config *scfg) xfree(saddr); } - list_for_each_entry(dns, &scfg->dnslookups, list) - gai_cancel(&dns->gcb); + list_for_each_entry(dns, &scfg->dnslookups, list) { + if (dns->pending) + gai_cancel(&dns->gcb); + } } bool scfg_init(struct server_config *scfg, const char *filename) @@ -547,6 +540,11 @@ bool scfg_init(struct server_config *scfg, const char *filename) INIT_LIST_HEAD(&scfg->locals); INIT_LIST_HEAD(&scfg->rcons); INIT_LIST_HEAD(&scfg->dnslookups); + scfg->gcbs = NULL; + scfg->dns_sev.sigev_notify = SIGEV_SIGNAL; + scfg->dns_sev.sigev_signo = SIGUSR1; + scfg->dns_sev.sigev_value.sival_ptr = scfg; + return true; } @@ -608,17 +606,13 @@ static char *get_line(char **pos, unsigned *lineno) return begin; } -static bool dnslookup(const char *name, uint16_t port, struct cfg_value *rvalue, - unsigned *naddrs, bool async) +/* FIXME: we should gather these up and do all in one go */ +static bool cfg_dnslookup(const char *name, uint16_t port, struct cfg_value *rvalue, + unsigned *naddrs, 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; + struct addrinfo *results = NULL; int r; assert_return(!empty_str(name) && strlen(name) < sizeof(dns->name) && @@ -631,9 +625,17 @@ static bool dnslookup(const char *name, uint16_t port, struct cfg_value *rvalue, dns = zmalloc(sizeof(*dns)); if (!dns) { error("async DNS lookup of %s failed: %m", name); - goto out; + return false; } debug(DBG_DNS, "doing async DNS lookup of %s: %p", name, dns); + + dns->pending = false; + + dns->gcb.ar_name = dns->name; + dns->gcb.ar_service = dns->port; + dns->gcb.ar_request = &dns->req; + + rvalue->dns_async = dns; } else { memset(&tmp, 0, sizeof(tmp)); dns = &tmp; @@ -648,70 +650,17 @@ static bool dnslookup(const char *name, uint16_t port, struct cfg_value *rvalue, 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 }; + if (async) + return true; - r = getaddrinfo_a(mode, gcbs, ARRAY_SIZE(gcbs), &dns->sev); + r = getaddrinfo(dns->name, dns->port, &dns->req, &results); if (r != 0) { - error("getaddrinfo(%s:%" PRIu16 "): %s", name, port, + error("getaddrinfo(%s:%s): %s", dns->name, dns->port, gai_strerror(r)); - goto out; - } - - if (async) { - rvalue->dns_async = dns; - rv = true; - goto out; - } - - results = dns->gcb.ar_result; - - /* FIXME: parts can be shared with the async cb? */ - 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); - debug(DBG_CFG, "addrstr: %s", saddr->addrstr); - list_add(&saddr->list, &rvalue->saddrs); - (*naddrs)++; - break; - - case AF_INET6: - in6 = (struct sockaddr_in6 *)ai->ai_addr; - saddr_set_ipv6(saddr, &in6->sin6_addr, in6->sin6_port); - debug(DBG_CFG, "addrstr: %s", saddr->addrstr); - list_add(&saddr->list, &rvalue->saddrs); - (*naddrs)++; - break; - - default: - error("getaddrinfo(%s:%s): unknown address family (%i)", - dns->name, dns->port, ai->ai_family); - xfree(saddr); - break; - } + return false; } - rv = true; - -out: - freeaddrinfo(results); - return rv; + return handle_addrinfo_results(results, &rvalue->saddrs, naddrs); } bool strtosockaddrs(const char *str, struct cfg_value *rvalue, bool async) @@ -808,7 +757,7 @@ bool strtosockaddrs(const char *str, struct cfg_value *rvalue, bool async) xfree(saddr); debug(DBG_CFG, "maybe got a hostname:port (%s:%" PRIu16 ")", str, port); - if (!dnslookup(str, port, rvalue, &naddrs, async)) + if (!cfg_dnslookup(str, port, rvalue, &naddrs, async)) goto error; } else if (strtou16_strict(tmp, &port) == 0) { -- cgit v1.2.3