| /* |
| * |
| * DNS Client - provides an asynchronous DNS resolution client. |
| * |
| * The client is implemented using the c-ares library and integrated with |
| * flimflam's glib main event loop. See http://c-ares.haxx.se and |
| * http://developer.gnome.org/glib for c-ares and glib documentation. |
| * |
| * This file originally created by Google, Inc. |
| * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <netdb.h> |
| #include <sys/time.h> |
| |
| #include <ares.h> |
| #include <glib.h> |
| |
| #define CONNMAN_API_SUBJECT_TO_CHANGE |
| #include <connman/assert.h> |
| #include <connman/device.h> |
| #include <connman/dns_client.h> |
| #include <connman/log.h> |
| #include <connman/resolver.h> |
| |
| #include "connman.h" |
| |
| #define _DBG_DNS_CLIENT(fmt, arg...) DBG(DBG_RESOLV, fmt, ## arg) |
| |
| /* Structure representing a pending asynchronous name resolution request. */ |
| struct ares_request { |
| char *hostname; /* hostname that we're resolving */ |
| struct connman_device *device; /* outgoing interface for request */ |
| struct timeval timeout; /* caller-specified timeout */ |
| struct timeval start_time; /* time at which request was started */ |
| connman_dns_client_callback_t cb; /* client-provided callback */ |
| void *data; /* user data */ |
| ares_channel channel; /* opaque, used by c-ares library */ |
| GHashTable *ares_watches; /* fds that we're monitoring for c-ares */ |
| guint timeout_source_id; /* glib source id for our ares timeout */ |
| gboolean running; /* stopped requests are eligible for deletion */ |
| }; |
| |
| /* |
| * Structure representing a file descriptor that we're monitoring within our |
| * glib event loop for c-ares. |
| */ |
| struct ares_watch { |
| struct ares_request *request; /* backpointer to our owner */ |
| int fd; /* file descriptor that we're watching */ |
| GIOChannel *gio_channel; /* glib IO channel */ |
| GIOCondition gio_condition; /* events in which we're interested */ |
| guint g_source_id; /* glib source id */ |
| }; |
| |
| /* |
| * List of pending asynchronous name resolution requests. We expect the number |
| * of pending requests to be small, hence the use of a linked list. |
| */ |
| static GList *pending_requests = NULL; |
| |
| /* |
| * ares requests are often stopped from within ares callbacks. In these cases, |
| * we defer deletion of the ares_request struct to the idle loop. This is the |
| * glib source id associated with the deferred deletion task. |
| */ |
| static guint deferred_deletion_g_source_id = 0; |
| |
| static void reset_ares_timeout(struct ares_request *request, |
| gboolean destroy_old_source); |
| static void stop_ares_request(struct ares_request *request); |
| |
| /* |
| * Callback invoked when it's time to give control back to c-ares. Controlled by |
| * the glib source referred to by |timeout_source_id| in struct ares_request. |
| */ |
| static gboolean ares_timeout_cb(gpointer data) |
| { |
| struct ares_request *request = data; |
| const gboolean destroy_old_source = FALSE; |
| |
| _DBG_DNS_CLIENT("request %p: running = %d", request, request->running); |
| |
| if (!request->running) { |
| request->timeout_source_id = 0; |
| return FALSE; |
| } |
| |
| ares_process_fd(request->channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD); |
| |
| /* |
| * NOTE: We tell reset_ares_timeout not to destroy its old timer source |
| * because we're calling it from within that source and it will be |
| * destroyed by glib when we return FALSE below. |
| */ |
| reset_ares_timeout(request, destroy_old_source); |
| |
| /* |
| * Return FALSE to get rid of our old glib source. We created a new |
| * one during our call to reset_ares_timeout above. |
| */ |
| return FALSE; |
| } |
| |
| /* |
| * Determine how long c-ares is willing to wait until being given control and |
| * schedule ares_timeout_cb to be invoked at that time. Any existing |
| * timer is replaced. If |destroy_old_source| is TRUE, the old timer's glib |
| * source will be destroyed. |
| */ |
| static void reset_ares_timeout(struct ares_request *request, |
| gboolean destroy_old_source) |
| { |
| struct timeval ret_tv, now, elapsed, max_tv; |
| struct timeval *tv; |
| struct timeval *max = NULL; |
| guint timeout_interval_msecs = 0; |
| gboolean timeout_provided = FALSE; |
| |
| _DBG_DNS_CLIENT("request %p: running = %d", request, request->running); |
| |
| if (!request->running) |
| return; |
| |
| /* |
| * Compute how much time has elapsed since the request started. |
| * If the client provided a non-default timeout and we've timed out, |
| * notify the client and stop the request. |
| */ |
| gettimeofday(&now, NULL); |
| timersub(&now, &request->start_time, &elapsed); |
| timeout_provided = request->timeout.tv_sec != 0 || |
| request->timeout.tv_usec != 0; |
| if (timeout_provided && timercmp(&elapsed, &request->timeout, >=)) { |
| request->cb(request->data, CONNMAN_DNS_CLIENT_ERROR_TIMED_OUT, |
| NULL); |
| stop_ares_request(request); |
| return; |
| } |
| |
| /* |
| * Tell c-ares how long we're willing to wait (max) and see if it wants |
| * to regain control sooner than that. |
| */ |
| if (timeout_provided) { |
| timersub(&request->timeout, &elapsed, &max_tv); |
| max = &max_tv; |
| } |
| if ((tv = ares_timeout(request->channel, max, &ret_tv)) == NULL) { |
| connman_error("%s: ares_timeout failed", __func__); |
| return; |
| } |
| |
| /* |
| * Reschedule our timeout to be the sooner of the ares-specified tiemout |
| * and the client-specified timeout. |
| */ |
| if (request->timeout_source_id != 0 && destroy_old_source) { |
| if (!g_source_remove(request->timeout_source_id)) |
| _DBG_DNS_CLIENT("g_source_remove failed"); |
| } |
| |
| timeout_interval_msecs = tv->tv_sec * 1000 + tv->tv_usec / 1000; |
| _DBG_DNS_CLIENT("timeout interval = %u", timeout_interval_msecs); |
| |
| request->timeout_source_id = g_timeout_add(timeout_interval_msecs, |
| ares_timeout_cb, |
| request); |
| } |
| |
| /* |
| * Callback invoked by glib when there is activity on a file descriptor that |
| * we're monitoring for c-ares. |
| */ |
| static gboolean ares_watch_io_cb(GIOChannel *source, |
| GIOCondition condition, |
| gpointer data) |
| { |
| struct ares_watch *watch = data; |
| ares_socket_t read_fd = ARES_SOCKET_BAD; |
| ares_socket_t write_fd = ARES_SOCKET_BAD; |
| const gboolean destroy_old_source = TRUE; |
| |
| _DBG_DNS_CLIENT("watch %p (fd %d): condition = 0x%x", watch, watch->fd, |
| condition); |
| |
| if (!watch->request->running) { |
| /* Destroy this source by returning FALSE. */ |
| watch->g_source_id = 0; |
| return FALSE; |
| } |
| |
| if (condition & (G_IO_NVAL | G_IO_HUP | G_IO_ERR)) { |
| connman_error("%s: error condition on fd %d", __func__, |
| watch->fd); |
| watch->g_source_id = 0; |
| return FALSE; |
| } |
| |
| if (condition & G_IO_IN) |
| read_fd = watch->fd; |
| if (condition & G_IO_OUT) |
| write_fd = watch->fd; |
| |
| /* Give control to c-ares. */ |
| ares_process_fd(watch->request->channel, read_fd, write_fd); |
| |
| reset_ares_timeout(watch->request, destroy_old_source); |
| |
| return TRUE; |
| } |
| |
| /* |
| * Destroy an ares_watch structure. We register this as our value destroy |
| * function when creating the ares_watches table, and it is called by glib |
| * whenever we remove a value from the table or destroy the table. |
| */ |
| static void destroy_ares_watch(gpointer data) |
| { |
| struct ares_watch *watch = data; |
| |
| CONNMAN_ASSERT(!watch->request->running); |
| |
| _DBG_DNS_CLIENT("watch %p (fd %d)", watch, watch->fd); |
| |
| if (watch->g_source_id != 0) { |
| if (!g_source_remove(watch->g_source_id)) { |
| _DBG_DNS_CLIENT("g_source_remove failed for id %d", |
| watch->g_source_id); |
| } |
| watch->g_source_id = 0; |
| } |
| |
| g_io_channel_unref(watch->gio_channel); |
| g_free(watch); |
| } |
| |
| /* |
| * Create an ares_watch for |fd| and store it in the ares_watches table for |
| * |request|. Monitor for readability if |read| is TRUE. Monitor for writability |
| * if |write| is TRUE. If there is already an entry for |fd| in the table, |
| * update it according to the values of |read| and |write|. |
| */ |
| static gboolean init_ares_watch(struct ares_request *request, int fd, |
| gboolean read, gboolean write) |
| { |
| struct ares_watch *watch; |
| |
| _DBG_DNS_CLIENT("fd = %d, read = %d, write = %d", fd, read, write); |
| |
| CONNMAN_ASSERT(request->running); |
| |
| /* |
| * If there's an old watch in the table, destroy it. We'll replace it |
| * with a new one below if c-ares is still interested in this fd. |
| */ |
| if (g_hash_table_lookup(request->ares_watches, &fd) != NULL) { |
| /* This removal calls destroy_ares_watch on the old watch. */ |
| g_hash_table_remove(request->ares_watches, &fd); |
| } |
| |
| if (!read && !write) |
| return TRUE; |
| |
| watch = g_malloc0(sizeof(struct ares_watch)); |
| |
| watch->request = request; |
| watch->fd = fd; |
| watch->g_source_id = 0; |
| |
| watch->gio_condition = G_IO_NVAL | G_IO_HUP | G_IO_ERR; |
| if (read) |
| watch->gio_condition |= G_IO_IN; |
| if (write) |
| watch->gio_condition |= G_IO_OUT; |
| |
| watch->gio_channel = g_io_channel_unix_new(fd); |
| if (watch->gio_channel == NULL) { |
| connman_error("%s: could not create g_io_channel for fd %d", |
| __func__, fd); |
| g_free(watch); |
| return FALSE; |
| } |
| g_io_channel_set_close_on_unref(watch->gio_channel, FALSE); |
| |
| g_hash_table_insert(request->ares_watches, &fd, watch); |
| |
| watch->g_source_id = g_io_add_watch(watch->gio_channel, |
| watch->gio_condition, |
| ares_watch_io_cb, |
| watch); |
| |
| return TRUE; |
| } |
| |
| /* |
| * Destroy an ares_request struct, freeing the resources allocated in |
| * init_ares_request. |request| must already have been removed from the |
| * |pending_requests| list and must have been marked not running. |
| */ |
| static void destroy_ares_request(struct ares_request *request) |
| { |
| _DBG_DNS_CLIENT("request %p", request); |
| |
| CONNMAN_ASSERT(!request->running); |
| |
| ares_destroy(request->channel); |
| g_free(request->hostname); |
| if (request->timeout_source_id != 0) |
| g_source_remove(request->timeout_source_id); |
| /* Hash table destruction calls destroy_ares_watch on all watches. */ |
| g_hash_table_destroy(request->ares_watches); |
| if (request->device != NULL) { |
| connman_device_rp_filter_enable(request->device); |
| connman_device_unref(request->device); |
| } |
| g_free(request); |
| } |
| |
| /* |
| * Callback invoked from the main loop to perform deferred deletion of stopped |
| * ares_request objects. We do deferred deletion to avoid problems when we're in |
| * an ares callback and want to delete an object that contains context |
| * associated with that callback. |
| */ |
| static gboolean delete_stopped_ares_requests_cb(gpointer data) |
| { |
| GList *node, *next; |
| struct ares_request *request; |
| guint num_requests_deleted = 0; |
| |
| _DBG_DNS_CLIENT("pending_requests list has length %u", |
| g_list_length(pending_requests)); |
| |
| /* |
| * Inspect each request in |pending_requests| and destroy it if it's |
| * not running. |
| */ |
| for (node = pending_requests; node != NULL; node = next) { |
| next = g_list_next(node); |
| request = node->data; |
| if (!request->running) { |
| pending_requests = g_list_delete_link(pending_requests, |
| node); |
| destroy_ares_request(request); |
| ++num_requests_deleted; |
| } |
| } |
| _DBG_DNS_CLIENT("deleted %u stopped requests", num_requests_deleted); |
| |
| deferred_deletion_g_source_id = 0; |
| return FALSE; |
| } |
| |
| |
| /* |
| * Stop an ares_request and schedule the deferred deletion task if it's |
| * not already running. |
| */ |
| static void stop_ares_request(struct ares_request *request) |
| { |
| _DBG_DNS_CLIENT(""); |
| |
| request->running = FALSE; |
| |
| if (deferred_deletion_g_source_id != 0) |
| return; |
| |
| deferred_deletion_g_source_id = |
| g_idle_add(delete_stopped_ares_requests_cb, NULL); |
| if (deferred_deletion_g_source_id == 0) |
| connman_error("%s: g_idle_add failed", __func__); |
| } |
| |
| /* |
| * Callback that is invoked by c-ares to tell us which sockets it wants us to |
| * monitor for readability and writability. |
| */ |
| static void ares_socket_state_cb(void *data, int s, int read, int write) |
| { |
| struct ares_request *request = (struct ares_request *)data; |
| |
| _DBG_DNS_CLIENT(""); |
| |
| if (!request->running) |
| return; |
| |
| _DBG_DNS_CLIENT("socket %d: read = %d, write = %d", s, read, write); |
| |
| if (!init_ares_watch(request, s, read, write)) |
| connman_error("%s: couldn't create ares_watch for socket %d", |
| __func__, s); |
| } |
| |
| /* |
| * Converts a c-ares status code to the corresponding dns_client status code. |
| * We do this to completely encapsulate c-ares. In theory, we should be able to |
| * replace it with a different asynchronous DNS library without changing our |
| * clients. |
| */ |
| static connman_dns_client_status_t status_from_ares_status(int ares_status) |
| { |
| switch(ares_status) { |
| case ARES_SUCCESS: |
| return CONNMAN_DNS_CLIENT_SUCCESS; |
| case ARES_ENODATA: |
| return CONNMAN_DNS_CLIENT_ERROR_NO_DATA; |
| case ARES_EFORMERR: |
| return CONNMAN_DNS_CLIENT_ERROR_FORM_ERR; |
| case ARES_ESERVFAIL: |
| return CONNMAN_DNS_CLIENT_ERROR_SERVER_FAIL; |
| case ARES_ENOTFOUND: |
| return CONNMAN_DNS_CLIENT_ERROR_NOT_FOUND; |
| case ARES_ENOTIMP: |
| return CONNMAN_DNS_CLIENT_ERROR_NOT_IMP; |
| case ARES_EREFUSED: |
| return CONNMAN_DNS_CLIENT_ERROR_REFUSED; |
| case ARES_EBADQUERY: |
| case ARES_EBADNAME: |
| case ARES_EBADFAMILY: |
| case ARES_EBADRESP: |
| return CONNMAN_DNS_CLIENT_ERROR_BAD_QUERY; |
| case ARES_ECONNREFUSED: |
| return CONNMAN_DNS_CLIENT_ERROR_NET_REFUSED; |
| case ARES_ETIMEOUT: |
| return CONNMAN_DNS_CLIENT_ERROR_TIMED_OUT; |
| default: |
| return CONNMAN_DNS_CLIENT_ERROR_UNKNOWN; |
| } |
| } |
| |
| /* |
| * Returns a human-friendly error string corresponding to |status|. |
| * The strings that we return are intentionally consistent with shill error |
| * messages. |
| */ |
| const char *connman_dns_client_strerror(connman_dns_client_status_t status) |
| { |
| switch(status) { |
| case CONNMAN_DNS_CLIENT_SUCCESS: |
| return "The query was successful."; |
| case CONNMAN_DNS_CLIENT_ERROR_NO_DATA: |
| return "The query response contains no answers."; |
| case CONNMAN_DNS_CLIENT_ERROR_FORM_ERR: |
| return "The server says the query is bad."; |
| case CONNMAN_DNS_CLIENT_ERROR_SERVER_FAIL: |
| return "The server says it had a failure."; |
| case CONNMAN_DNS_CLIENT_ERROR_NOT_FOUND: |
| return "The queried-for domain was not found."; |
| case CONNMAN_DNS_CLIENT_ERROR_NOT_IMP: |
| return "The server doesn't implement operation."; |
| case CONNMAN_DNS_CLIENT_ERROR_REFUSED: |
| return "The server replied, refused the query."; |
| case CONNMAN_DNS_CLIENT_ERROR_BAD_QUERY: |
| return "Locally we could not format a query."; |
| case CONNMAN_DNS_CLIENT_ERROR_NET_REFUSED: |
| return "The network connection was refused."; |
| case CONNMAN_DNS_CLIENT_ERROR_TIMED_OUT: |
| return "The network connection was timed out."; |
| case CONNMAN_DNS_CLIENT_ERROR_UNKNOWN: |
| default: |
| return "DNS Resolver unknown internal error."; |
| } |
| } |
| |
| /* |
| * Callback that is invoked by c-ares when an asynchronous name resolution |
| * request that we have previously initiated is complete. |
| */ |
| static void ares_request_cb(void *arg, int ares_status, int timeouts, |
| struct hostent *hostent) |
| { |
| struct sockaddr_in sin; |
| struct sockaddr_in6 sin6; |
| size_t addr_length; |
| void *addr_buffer; |
| char ip_addr_string[INET6_ADDRSTRLEN]; |
| struct sockaddr *ip_addr; |
| struct ares_request *request = (struct ares_request *)arg; |
| |
| _DBG_DNS_CLIENT(""); |
| |
| if (!request->running) |
| return; |
| |
| /* Stop the request. It will be deleted later from the idle loop. */ |
| stop_ares_request(request); |
| |
| if (ares_status != ARES_SUCCESS) { |
| _DBG_DNS_CLIENT("ares request for '%s' failed: %s", |
| request->hostname, ares_strerror(ares_status)); |
| /* Notify client. */ |
| request->cb(request->data, status_from_ares_status(ares_status), |
| NULL); |
| return; |
| } |
| |
| if (hostent->h_addrtype != AF_INET && hostent->h_addrtype != AF_INET6) { |
| _DBG_DNS_CLIENT("unsupported addrtype: %d", |
| hostent->h_addrtype); |
| request->cb(request->data, CONNMAN_DNS_CLIENT_ERROR_NO_DATA, |
| NULL); |
| return; |
| } |
| |
| if (hostent->h_addrtype == AF_INET) { |
| memset(&sin, 0, sizeof(sin)); |
| sin.sin_family = AF_INET; |
| addr_length = sizeof(sin.sin_addr.s_addr); |
| addr_buffer = &sin.sin_addr.s_addr; |
| ip_addr = (struct sockaddr *) &sin; |
| } else { /* AF_INET6 */ |
| memset(&sin6, 0, sizeof(sin6)); |
| sin6.sin6_family = AF_INET6; |
| addr_length = sizeof(sin6.sin6_addr.s6_addr); |
| addr_buffer = &sin6.sin6_addr.s6_addr; |
| ip_addr = (struct sockaddr *) &sin6; |
| } |
| |
| if (hostent->h_length > addr_length) { |
| _DBG_DNS_CLIENT("address too large: %u bytes", |
| hostent->h_length); |
| request->cb(request->data, CONNMAN_DNS_CLIENT_ERROR_NO_DATA, |
| NULL); |
| return; |
| } |
| |
| memcpy(addr_buffer, hostent->h_addr, hostent->h_length); |
| |
| if (inet_ntop(hostent->h_addrtype, addr_buffer, ip_addr_string, |
| sizeof(ip_addr_string)) == NULL) { |
| _DBG_DNS_CLIENT("could not convert address to string: %s", |
| strerror(errno)); |
| request->cb(request->data, CONNMAN_DNS_CLIENT_ERROR_NO_DATA, |
| NULL); |
| return; |
| } |
| |
| _DBG_DNS_CLIENT("ares request for '%s' succeeded with %d timeouts: %s", |
| request->hostname, timeouts, ip_addr_string); |
| request->cb(request->data, status_from_ares_status(ares_status), |
| ip_addr); |
| } |
| |
| /* Cancel all in-progress asynchronous name resolution requests. */ |
| static void cancel_all_ares_requests() |
| { |
| GList *node; |
| struct ares_request *request; |
| |
| _DBG_DNS_CLIENT(""); |
| |
| while ((node = g_list_first(pending_requests)) != NULL) { |
| request = node->data; |
| pending_requests = g_list_delete_link(pending_requests, node); |
| request->running = FALSE; /* don't trip assertion */ |
| destroy_ares_request(request); |
| } |
| } |
| |
| /* |
| * Retrieve the nameservers that are configured for |device| and set |options| |
| * and |optmask| accordingly so that c-ares will use these nameservers when |
| * issuing DNS requests. |
| */ |
| static void set_ares_nameserver_options(struct connman_device *device, |
| struct ares_options *options, |
| int *optmask) { |
| const char *interface; |
| const struct connman_resolver_state *resolver_state; |
| char **serverp; |
| struct in_addr addr; |
| int i; |
| |
| if (device == NULL) |
| return; |
| |
| interface = connman_device_get_interface(device); |
| if (interface == NULL) { |
| connman_error("%s: no interface for device %p", __func__, |
| device); |
| return; |
| } |
| |
| resolver_state = connman_resolver_lookup(interface); |
| if (resolver_state == NULL) { |
| connman_error("%s: no resolver state for interface %s", |
| __func__, interface); |
| return; |
| } |
| |
| /* |
| * resolver_state doesn't tell us how many server addresses are present, |
| * so we need to walk the list once to determine its length so that we |
| * can allocate the correct number of struct in_addrs and a second time |
| * to convert each element from a string to a struct in_addr. |
| */ |
| options->nservers = 0; |
| for (serverp = resolver_state->servers; *serverp != NULL; ++serverp) |
| ++options->nservers; |
| |
| _DBG_DNS_CLIENT("%d servers found for interface %s", options->nservers, |
| interface); |
| |
| if (options->nservers == 0) { |
| connman_error("%s: no DNS server addresses for interface %s", |
| __func__, interface); |
| return; |
| } |
| |
| options->servers = g_malloc0(options->nservers * |
| sizeof(*options->servers)); |
| /* options->servers is freed in free_ares_nameserver_options. */ |
| |
| i = 0; |
| for (serverp = resolver_state->servers; *serverp != NULL; ++serverp) { |
| if (inet_aton(*serverp, &addr) == 0) { |
| _DBG_DNS_CLIENT("invalid server %s for interface %s", |
| *serverp, interface); |
| /* Omit this server from the list. */ |
| --options->nservers; |
| continue; |
| } |
| _DBG_DNS_CLIENT("found nameserver %s for interface %s", |
| *serverp, interface); |
| options->servers[i] = addr; |
| ++i; |
| } |
| |
| if (options->nservers == 0) { |
| connman_error("%s: no valid DNS server addrs for interface %s", |
| __func__, interface); |
| return; |
| } |
| |
| *optmask |= ARES_OPT_SERVERS; |
| } |
| |
| /* |
| * Free memory allocated by set_ares_nameserver_options. |
| */ |
| static void free_ares_nameserver_options(struct ares_options *options) |
| { |
| g_free(options->servers); |
| options->servers = NULL; |
| options->nservers = 0; |
| } |
| |
| /* Initiate an asynchronous name resolution request. */ |
| connman_dns_client_request_t |
| connman_dns_client_submit_request(const char *hostname, |
| struct connman_device *device, |
| int timeout_ms, |
| connman_dns_client_callback_t cb, |
| void *data) |
| { |
| int ares_status; |
| struct ares_request *request; |
| struct ares_options options; |
| int optmask; |
| const gboolean destroy_old_source = TRUE; |
| |
| _DBG_DNS_CLIENT(""); |
| |
| if (timeout_ms < 0) { |
| _DBG_DNS_CLIENT("invalid timeout value of %d ms", timeout_ms); |
| return NULL; |
| } |
| |
| request = g_malloc0(sizeof(struct ares_request)); |
| request->running = TRUE; |
| |
| request->ares_watches = g_hash_table_new_full(g_int_hash, g_int_equal, |
| NULL, destroy_ares_watch); |
| if (request->ares_watches == NULL) { |
| _DBG_DNS_CLIENT("could not create ares_watches table"); |
| g_free(request); |
| return NULL; |
| } |
| |
| /* |
| * Init a c-ares channel for this request. We set an option asking |
| * c-ares to notify us via callback about which sockets it wants to |
| * monitor for readability and writability. This allows us to |
| * integrate c-ares activity into our glib main event loop. |
| */ |
| memset(&options, 0, sizeof(options)); |
| options.sock_state_cb = ares_socket_state_cb; |
| options.sock_state_cb_data = request; |
| optmask = ARES_OPT_SOCK_STATE_CB; |
| if (timeout_ms > 0) { |
| options.timeout = timeout_ms; |
| optmask |= ARES_OPT_TIMEOUTMS; |
| } |
| set_ares_nameserver_options(device, &options, &optmask); |
| ares_status = ares_init_options(&request->channel, &options, optmask); |
| free_ares_nameserver_options(&options); |
| if (ares_status != ARES_SUCCESS) { |
| connman_error("%s: failed to init c-ares channel: %s", __func__, |
| ares_strerror(ares_status)); |
| request->running = FALSE; /* don't trip assertion */ |
| g_hash_table_destroy(request->ares_watches); |
| g_free(request); |
| return NULL; |
| } |
| |
| /* |
| * If the caller has provided a preferred interface, tell c-ares to |
| * send requests out that interface and disable rp filter for the |
| * duration of the request so that we can receive incoming responses. |
| */ |
| request->device = NULL; |
| if (device != NULL) { |
| _DBG_DNS_CLIENT("caller has specified device %s", |
| connman_device_get_interface(device)); |
| request->device = connman_device_ref(device); |
| ares_set_local_dev(request->channel, |
| connman_device_get_interface(device)); |
| connman_device_rp_filter_disable(request->device); |
| } |
| |
| request->cb = cb; |
| request->data = data; |
| request->hostname = g_strdup(hostname); |
| request->timeout.tv_sec = timeout_ms / 1000; |
| request->timeout.tv_usec = (timeout_ms % 1000) * 1000; |
| gettimeofday(&request->start_time, NULL); |
| |
| pending_requests = g_list_append(pending_requests, request); |
| |
| ares_gethostbyname(request->channel, hostname, AF_INET, ares_request_cb, |
| request); |
| |
| reset_ares_timeout(request, destroy_old_source); |
| |
| return request; |
| } |
| |
| /* Cancel an in-progress name resolution request. */ |
| void connman_dns_client_cancel_request(connman_dns_client_request_t request) |
| { |
| _DBG_DNS_CLIENT("request %p", request); |
| |
| if (request == NULL || !request->running) |
| return; |
| |
| pending_requests = g_list_remove(pending_requests, request); |
| request->running = FALSE; /* don't trip assertion */ |
| destroy_ares_request(request); |
| } |
| |
| /* Intitialize this module. */ |
| int __connman_dns_client_init(void) |
| { |
| int ares_status = 0; |
| _DBG_DNS_CLIENT(""); |
| ares_status = ares_library_init(ARES_LIB_INIT_ALL); |
| if (ares_status != ARES_SUCCESS) { |
| connman_error("%s: Failed to init c-ares: %s", __func__, |
| ares_strerror(ares_status)); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* Clean up. */ |
| void __connman_dns_client_cleanup(void) |
| { |
| _DBG_DNS_CLIENT(""); |
| if (deferred_deletion_g_source_id != 0) { |
| g_source_remove(deferred_deletion_g_source_id); |
| deferred_deletion_g_source_id = 0; |
| } |
| cancel_all_ares_requests(); |
| /* We rely on libcurl to call ares_library_cleanup() */ |
| } |