| /* |
| * Hostroute - create host routes for cellular services based on each |
| * service's Cellular.UsageUrl property. |
| * |
| * This file initially 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 <errno.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| |
| #include <glib.h> |
| |
| #define CONNMAN_API_SUBJECT_TO_CHANGE |
| #include <connman/assert.h> |
| #include <connman/dns_client.h> |
| #include <connman/plugin.h> |
| #include <connman/log.h> |
| #include <connman/notifier.h> |
| #include <connman/service.h> |
| |
| #define _DBG_HOSTROUTE(fmt, arg...) DBG(DBG_HOSTROUTE, fmt, ## arg) |
| |
| /* |
| * Represents a pending DNS request for |hostname|. We will use the result |
| * to install a host route through the interface associated with |service|. |
| */ |
| struct dns_request { |
| struct connman_service *service; |
| char *hostname; |
| connman_dns_client_request_t handle; /* opaque to us */ |
| }; |
| |
| /* |
| * List of pending asynchronous name resolution requests. We expect the number |
| * of pending requests to be small, probably one, because we allow only one |
| * pending request per cellular service, and there is typically one active |
| * cellular service per device. |
| */ |
| static GList *pending_dns_requests = NULL; |
| |
| /* |
| * Destroy a dns_request struct, freeing the resources allocated in |
| * submit_dns_request. The request must already have been removed from the |
| * |pending_dns_requests| list. |
| */ |
| static void destroy_dns_request(struct dns_request *request) |
| { |
| _DBG_HOSTROUTE("request %p", request); |
| connman_dns_client_cancel_request(request->handle); |
| connman_service_unref(request->service); |
| g_free(request->hostname); |
| g_free(request); |
| } |
| |
| /* |
| * Callback that is invoked by c-ares when an asynchronous name resolution |
| * request that we have previously initiated is complete. |
| */ |
| static void dns_request_cb(void *data, |
| connman_dns_client_status_t status, |
| struct sockaddr *ip_addr) |
| { |
| struct dns_request *request = data; |
| char ip_addr_string[INET_ADDRSTRLEN]; |
| struct sockaddr_in *sin; |
| |
| _DBG_HOSTROUTE("request %p: status = %d", request, status); |
| |
| pending_dns_requests = g_list_remove(pending_dns_requests, request); |
| |
| if (status != CONNMAN_DNS_CLIENT_SUCCESS) { |
| connman_error("%s: ares request for '%s' failed: %s", __func__, |
| request->hostname, |
| connman_dns_client_strerror(status)); |
| destroy_dns_request(request); |
| return; |
| } |
| |
| /* |
| * We only accept IPv4 results because the sticky route code and the |
| * underlying host route code that it uses only work with IPv4 addrs. |
| */ |
| if (ip_addr->sa_family != AF_INET) { |
| connman_error("%s: invalid addr family: %d", __func__, |
| ip_addr->sa_family); |
| destroy_dns_request(request); |
| return; |
| } |
| |
| /* Convert sockaddr into a dotted decimal string. */ |
| sin = (struct sockaddr_in *) ip_addr; |
| if (inet_ntop(sin->sin_family, &sin->sin_addr, ip_addr_string, |
| sizeof(ip_addr_string)) == NULL) { |
| connman_error("%s: could not convert address to string: %s", |
| __func__, strerror(errno)); |
| destroy_dns_request(request); |
| return; |
| } |
| _DBG_HOSTROUTE("dns request for '%s' succeeded: %s", request->hostname, |
| ip_addr_string); |
| |
| /* Ask the flimflam service core to create and maintain a host route. */ |
| connman_service_create_sticky_route(request->service, ip_addr_string); |
| |
| destroy_dns_request(request); |
| } |
| |
| /* |
| * Initiate an asynchronous name resolution request for |hostname| in the |
| * context of |service|. Returns TRUE on success and FALSE on failure. Success |
| * indicates only that the request was initiated, not the success of the |
| * request itself. |
| */ |
| static gboolean submit_dns_request(struct connman_service *service, |
| const char *hostname) |
| { |
| struct dns_request *request; |
| struct connman_device *device; |
| int timeout_ms = 0; /* use default timeout */ |
| |
| _DBG_HOSTROUTE("service %p: hostname = %s", service, hostname); |
| |
| request = g_malloc0(sizeof(struct dns_request)); |
| request->service = connman_service_ref(service); |
| request->hostname = g_strdup(hostname); |
| |
| /* |
| * We want to send DNS requests on the interface associated with the |
| * cellular service. |
| */ |
| device = connman_service_get_device(request->service); |
| |
| request->handle = connman_dns_client_submit_request(request->hostname, |
| device, |
| timeout_ms, |
| dns_request_cb, |
| request); |
| if (request->handle == NULL) { |
| _DBG_HOSTROUTE("could not submit dns request for \"%s\"", |
| request->hostname); |
| destroy_dns_request(request); |
| return FALSE; |
| } |
| |
| pending_dns_requests = g_list_append(pending_dns_requests, request); |
| |
| return TRUE; |
| } |
| |
| /* Cancel any in-progress dns request for |service|. */ |
| static void cancel_dns_request(struct connman_service *service) |
| { |
| GList *node; |
| struct dns_request *request; |
| |
| _DBG_HOSTROUTE("service %p", service); |
| |
| for (node = pending_dns_requests; node != NULL; |
| node = g_list_next(node)) { |
| request = node->data; |
| if (request->service == service) |
| break; |
| } |
| |
| if (node == NULL) |
| return; |
| |
| pending_dns_requests = g_list_delete_link(pending_dns_requests, node); |
| destroy_dns_request(request); |
| } |
| |
| /* Cancel all in-progress dns requests. */ |
| static void cancel_all_dns_requests() |
| { |
| GList *node; |
| struct dns_request *request; |
| |
| _DBG_HOSTROUTE(""); |
| |
| while ((node = g_list_first(pending_dns_requests)) != NULL) { |
| request = node->data; |
| pending_dns_requests = g_list_delete_link(pending_dns_requests, |
| node); |
| destroy_dns_request(request); |
| } |
| } |
| |
| /* |
| * Parse the hostname component from a url. |
| * Returns a dynamically allocated string in |*hostname_out| that |
| * must be g_freed by the caller when no longer needed. |
| */ |
| static void hostname_from_url(const char *url, char **hostname_out) |
| { |
| const char *start, *end; |
| const char *path, *params, *query, *fragment, *port; |
| size_t num_bytes_to_copy; |
| |
| /* Ignore protocol prefix. */ |
| start = strstr(url, "://"); |
| if (start != NULL) |
| start += strlen("://"); |
| else |
| start = url; |
| |
| /* |
| * Isolate netloc component of the url. |
| * We search for the earliest delimiter representing the start of a |
| * path, params, query, or fragment component of the url. |
| * See RFC 1808 for details on url syntax. |
| */ |
| end = url + strlen(url); |
| path = strchr(start, '/'); |
| if (path != NULL) |
| end = path; |
| params = strchr(start, ';'); |
| if (params != NULL && params < end) |
| end = params; |
| query = strchr(start, '?'); |
| if (query != NULL && query < end) |
| end = query; |
| fragment = strchr(start, '#'); |
| if (fragment != NULL && fragment < end) |
| end = fragment; |
| |
| /* Strip off port if it's part of netloc, leaving hostname. */ |
| port = strchr(start, ':'); |
| if (port != NULL && port < end) |
| end = port; |
| |
| num_bytes_to_copy = end - start; |
| *hostname_out = g_strndup(start, num_bytes_to_copy); |
| } |
| |
| /* |
| * Notification handler that is called by flimflam core when service state |
| * changes. We begin the process of installing a sticky host route if the |
| * service is cellular, connected, and doesn't already have one. Once a host |
| * route is installed, it is managed by the flimflam core for the lifetime of |
| * the service. |
| */ |
| static void hostroute_service_state_changed(struct connman_service *service) |
| { |
| const char *type; |
| const char *state; |
| const char *usage_url; |
| const char *sticky_route; |
| char *hostname; |
| |
| _DBG_HOSTROUTE("service %p", service); |
| |
| sticky_route = connman_service_get_sticky_route(service); |
| if (sticky_route != NULL) |
| return; |
| |
| type = connman_service_get_type(service); |
| if (g_strcmp0(type, "cellular") != 0) |
| return; |
| |
| state = connman_service_get_state(service); |
| if (g_strcmp0(state, "ready") != 0 && |
| g_strcmp0(state, "online") != 0 && |
| g_strcmp0(state, "portal") != 0) { |
| cancel_dns_request(service); |
| return; |
| } |
| |
| usage_url = connman_service_get_usage_url(service); |
| if (usage_url == NULL) { |
| connman_error("%s: service %p: no usage url", __func__, |
| service); |
| return; |
| } |
| _DBG_HOSTROUTE("usage_url = %s", usage_url); |
| |
| hostname_from_url(usage_url, &hostname); |
| _DBG_HOSTROUTE("hostname = %s", hostname); |
| |
| cancel_dns_request(service); |
| |
| if (!submit_dns_request(service, hostname)) { |
| connman_error("%s: service %p: couldn't submit dns request for " |
| "%s", __func__, service, hostname); |
| /* fall through for cleanup */ |
| } |
| g_free(hostname); |
| |
| /* |
| * Host route installation continues in dns_request_cb when the name |
| * resolution that we've just initiated completes. |
| */ |
| } |
| |
| /* Define a notifier that will notify us on service state changes. */ |
| static struct connman_notifier hostroute_notifier = { |
| .name = "hostroute", |
| .priority = CONNMAN_NOTIFIER_PRIORITY_DEFAULT, |
| .service_state_changed = hostroute_service_state_changed |
| }; |
| |
| /* Initialize the plugin. */ |
| static int hostroute_init(void) |
| { |
| _DBG_HOSTROUTE(""); |
| if (connman_notifier_register(&hostroute_notifier) < 0) { |
| connman_error("%s: Failed to register hostroute notifier", |
| __func__); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* Clean up. */ |
| static void hostroute_exit(void) |
| { |
| _DBG_HOSTROUTE(""); |
| connman_notifier_unregister(&hostroute_notifier); |
| cancel_all_dns_requests(); |
| } |
| |
| /* Register this plugin with the flimflam plugin system. */ |
| CONNMAN_PLUGIN_DEFINE(hostroute, "Hostroute plugin", VERSION, |
| CONNMAN_PLUGIN_PRIORITY_DEFAULT, hostroute_init, hostroute_exit) |