diff options
Diffstat (limited to 'lib/resolver.c')
-rw-r--r-- | lib/resolver.c | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/lib/resolver.c b/lib/resolver.c new file mode 100644 index 0000000..99bf356 --- /dev/null +++ b/lib/resolver.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* C-Ares integration to Quagga mainloop + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <ares.h> +#include <ares_version.h> + +#include "typesafe.h" +#include "jhash.h" +#include "frrevent.h" +#include "lib_errors.h" +#include "resolver.h" +#include "command.h" +#include "xref.h" +#include "vrf.h" + +XREF_SETUP(); + +struct resolver_state { + ares_channel channel; + struct event_loop *master; + struct event *timeout; +}; + +static struct resolver_state state; +static bool resolver_debug; + +/* a FD doesn't necessarily map 1:1 to a request; we could be talking to + * multiple caches simultaneously, to see which responds fastest. + * Theoretically we could also be using the same fd for multiple lookups, + * but the c-ares API guarantees an n:1 mapping for fd => channel. + * + * Either way c-ares makes that decision and we just need to deal with + * whatever FDs it gives us. + */ + +DEFINE_MTYPE_STATIC(LIB, ARES_FD, "c-ares (DNS) file descriptor information"); +PREDECL_HASH(resolver_fds); + +struct resolver_fd { + struct resolver_fds_item itm; + + int fd; + struct resolver_state *state; + struct event *t_read, *t_write; +}; + +static int resolver_fd_cmp(const struct resolver_fd *a, + const struct resolver_fd *b) +{ + return numcmp(a->fd, b->fd); +} + +static uint32_t resolver_fd_hash(const struct resolver_fd *item) +{ + return jhash_1word(item->fd, 0xacd04c9e); +} + +DECLARE_HASH(resolver_fds, struct resolver_fd, itm, resolver_fd_cmp, + resolver_fd_hash); + +static struct resolver_fds_head resfds[1] = {INIT_HASH(resfds[0])}; + +static struct resolver_fd *resolver_fd_get(int fd, + struct resolver_state *newstate) +{ + struct resolver_fd ref = {.fd = fd}, *res; + + res = resolver_fds_find(resfds, &ref); + if (!res && newstate) { + res = XCALLOC(MTYPE_ARES_FD, sizeof(*res)); + res->fd = fd; + res->state = newstate; + resolver_fds_add(resfds, res); + + if (resolver_debug) + zlog_debug("c-ares registered FD %d", fd); + } + return res; +} + +static void resolver_fd_drop_maybe(struct resolver_fd *resfd) +{ + if (resfd->t_read || resfd->t_write) + return; + + if (resolver_debug) + zlog_debug("c-ares unregistered FD %d", resfd->fd); + + resolver_fds_del(resfds, resfd); + XFREE(MTYPE_ARES_FD, resfd); +} + +/* end of FD housekeeping */ + +static void resolver_update_timeouts(struct resolver_state *r); + +static void resolver_cb_timeout(struct event *t) +{ + struct resolver_state *r = EVENT_ARG(t); + + ares_process(r->channel, NULL, NULL); + resolver_update_timeouts(r); +} + +static void resolver_cb_socket_readable(struct event *t) +{ + struct resolver_fd *resfd = EVENT_ARG(t); + struct resolver_state *r = resfd->state; + + event_add_read(r->master, resolver_cb_socket_readable, resfd, resfd->fd, + &resfd->t_read); + /* ^ ordering important: + * ares_process_fd may transitively call EVENT_OFF(resfd->t_read) + * combined with resolver_fd_drop_maybe, so resfd may be free'd after! + */ + ares_process_fd(r->channel, resfd->fd, ARES_SOCKET_BAD); + resolver_update_timeouts(r); +} + +static void resolver_cb_socket_writable(struct event *t) +{ + struct resolver_fd *resfd = EVENT_ARG(t); + struct resolver_state *r = resfd->state; + + event_add_write(r->master, resolver_cb_socket_writable, resfd, + resfd->fd, &resfd->t_write); + /* ^ ordering important: + * ares_process_fd may transitively call EVENT_OFF(resfd->t_write) + * combined with resolver_fd_drop_maybe, so resfd may be free'd after! + */ + ares_process_fd(r->channel, ARES_SOCKET_BAD, resfd->fd); + resolver_update_timeouts(r); +} + +static void resolver_update_timeouts(struct resolver_state *r) +{ + struct timeval *tv, tvbuf; + + EVENT_OFF(r->timeout); + tv = ares_timeout(r->channel, NULL, &tvbuf); + if (tv) { + unsigned int timeoutms = tv->tv_sec * 1000 + tv->tv_usec / 1000; + + event_add_timer_msec(r->master, resolver_cb_timeout, r, + timeoutms, &r->timeout); + } +} + +static void ares_socket_cb(void *data, ares_socket_t fd, int readable, + int writable) +{ + struct resolver_state *r = (struct resolver_state *)data; + struct resolver_fd *resfd; + + resfd = resolver_fd_get(fd, (readable || writable) ? r : NULL); + if (!resfd) + return; + + assert(resfd->state == r); + + if (!readable) + EVENT_OFF(resfd->t_read); + else if (!resfd->t_read) + event_add_read(r->master, resolver_cb_socket_readable, resfd, + fd, &resfd->t_read); + + if (!writable) + EVENT_OFF(resfd->t_write); + else if (!resfd->t_write) + event_add_write(r->master, resolver_cb_socket_writable, resfd, + fd, &resfd->t_write); + + resolver_fd_drop_maybe(resfd); +} + + +static void ares_address_cb(void *arg, int status, int timeouts, + struct hostent *he) +{ + struct resolver_query *query = (struct resolver_query *)arg; + union sockunion addr[16]; + void (*callback)(struct resolver_query *, const char *, int, + union sockunion *); + size_t i; + + callback = query->callback; + query->callback = NULL; + + if (status != ARES_SUCCESS) { + if (resolver_debug) + zlog_debug("[%p] Resolving failed (%s)", + query, ares_strerror(status)); + + callback(query, ares_strerror(status), -1, NULL); + return; + } + + for (i = 0; i < array_size(addr) && he->h_addr_list[i] != NULL; i++) { + memset(&addr[i], 0, sizeof(addr[i])); + addr[i].sa.sa_family = he->h_addrtype; + switch (he->h_addrtype) { + case AF_INET: + memcpy(&addr[i].sin.sin_addr, + (uint8_t *)he->h_addr_list[i], he->h_length); + break; + case AF_INET6: + memcpy(&addr[i].sin6.sin6_addr, + (uint8_t *)he->h_addr_list[i], he->h_length); + break; + } + } + + if (resolver_debug) + zlog_debug("[%p] Resolved with %d results", query, (int)i); + + callback(query, NULL, i, &addr[0]); +} + +static void resolver_cb_literal(struct event *t) +{ + struct resolver_query *query = EVENT_ARG(t); + void (*callback)(struct resolver_query *, const char *, int, + union sockunion *); + + callback = query->callback; + query->callback = NULL; + + callback(query, ARES_SUCCESS, 1, &query->literal_addr); +} + +void resolver_resolve(struct resolver_query *query, int af, vrf_id_t vrf_id, + const char *hostname, + void (*callback)(struct resolver_query *, const char *, + int, union sockunion *)) +{ + int ret; + + if (hostname == NULL) + return; + + if (query->callback != NULL) { + flog_err( + EC_LIB_RESOLVER, + "Trying to resolve '%s', but previous query was not finished yet", + hostname); + return; + } + + query->callback = callback; + query->literal_cb = NULL; + + ret = str2sockunion(hostname, &query->literal_addr); + if (ret == 0) { + if (resolver_debug) + zlog_debug("[%p] Resolving '%s' (IP literal)", + query, hostname); + + /* for consistency with proper name lookup, don't call the + * callback immediately; defer to thread loop + */ + event_add_timer_msec(state.master, resolver_cb_literal, query, + 0, &query->literal_cb); + return; + } + + if (resolver_debug) + zlog_debug("[%p] Resolving '%s'", query, hostname); + + ret = vrf_switch_to_netns(vrf_id); + if (ret < 0) { + flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)", + __func__, vrf_id, safe_strerror(errno)); + return; + } + ares_gethostbyname(state.channel, hostname, af, ares_address_cb, query); + ret = vrf_switchback_to_initial(); + if (ret < 0) + flog_err_sys(EC_LIB_SOCKET, + "%s: Can't switchback from VRF %u (%s)", __func__, + vrf_id, safe_strerror(errno)); + resolver_update_timeouts(&state); +} + +DEFUN(debug_resolver, + debug_resolver_cmd, + "[no] debug resolver", + NO_STR + DEBUG_STR + "Debug DNS resolver actions\n") +{ + resolver_debug = (argc == 2); + return CMD_SUCCESS; +} + +static int resolver_config_write_debug(struct vty *vty); +static struct cmd_node resolver_debug_node = { + .name = "resolver debug", + .node = RESOLVER_DEBUG_NODE, + .prompt = "", + .config_write = resolver_config_write_debug, +}; + +static int resolver_config_write_debug(struct vty *vty) +{ + if (resolver_debug) + vty_out(vty, "debug resolver\n"); + return 1; +} + + +void resolver_init(struct event_loop *tm) +{ + struct ares_options ares_opts; + + state.master = tm; + + ares_opts = (struct ares_options){ + .sock_state_cb = &ares_socket_cb, + .sock_state_cb_data = &state, + .timeout = 2, + .tries = 3, + }; + + ares_init_options(&state.channel, &ares_opts, + ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUT + | ARES_OPT_TRIES); + + install_node(&resolver_debug_node); + install_element(CONFIG_NODE, &debug_resolver_cmd); + install_element(ENABLE_NODE, &debug_resolver_cmd); +} |