| /* |
| * |
| * Connection Manager |
| * |
| * Copyright (C) 2007-2013 Intel Corporation. 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 |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <resolv.h> |
| #include <netdb.h> |
| |
| #include "connman.h" |
| |
| #define RESOLVER_FLAG_PUBLIC (1 << 0) |
| |
| /* |
| * Threshold for RDNSS lifetime. Will be used to trigger RS |
| * before RDNSS entries actually expire |
| */ |
| #define RESOLVER_LIFETIME_REFRESH_THRESHOLD 0.8 |
| |
| struct entry_data { |
| int index; |
| char *domain; |
| char *server; |
| int family; |
| unsigned int flags; |
| unsigned int lifetime; |
| guint timeout; |
| }; |
| |
| static GSList *entry_list = NULL; |
| static bool dnsproxy_enabled = false; |
| |
| struct resolvfile_entry { |
| int index; |
| char *domain; |
| char *server; |
| }; |
| |
| static GList *resolvfile_list = NULL; |
| |
| static void resolvfile_remove_entries(GList *entries) |
| { |
| GList *list; |
| |
| for (list = entries; list; list = list->next) { |
| struct resolvfile_entry *entry = list->data; |
| |
| resolvfile_list = g_list_remove(resolvfile_list, entry); |
| |
| g_free(entry->server); |
| g_free(entry->domain); |
| g_free(entry); |
| } |
| |
| g_list_free(entries); |
| } |
| |
| static int resolvfile_export(void) |
| { |
| GList *list; |
| GString *content; |
| int fd, err; |
| unsigned int count; |
| mode_t old_umask; |
| |
| content = g_string_new("# Generated by Connection Manager\n"); |
| |
| /* |
| * Domains and nameservers are added in reverse so that the most |
| * recently appended entry is the primary one. No more than |
| * MAXDNSRCH/MAXNS entries are used. |
| */ |
| |
| for (count = 0, list = g_list_last(resolvfile_list); |
| list && (count < MAXDNSRCH); |
| list = g_list_previous(list)) { |
| struct resolvfile_entry *entry = list->data; |
| |
| if (!entry->domain) |
| continue; |
| |
| if (count == 0) |
| g_string_append_printf(content, "search "); |
| |
| g_string_append_printf(content, "%s ", entry->domain); |
| count++; |
| } |
| |
| if (count) |
| g_string_append_printf(content, "\n"); |
| |
| for (count = 0, list = g_list_last(resolvfile_list); |
| list && (count < MAXNS); |
| list = g_list_previous(list)) { |
| struct resolvfile_entry *entry = list->data; |
| |
| if (!entry->server) |
| continue; |
| |
| g_string_append_printf(content, "nameserver %s\n", |
| entry->server); |
| count++; |
| } |
| |
| old_umask = umask(022); |
| |
| fd = open("/etc/resolv.conf", O_RDWR | O_CREAT | O_CLOEXEC, |
| S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| if (fd < 0) { |
| err = -errno; |
| goto done; |
| } |
| |
| if (ftruncate(fd, 0) < 0) { |
| err = -errno; |
| goto failed; |
| } |
| |
| err = 0; |
| |
| if (write(fd, content->str, content->len) < 0) |
| err = -errno; |
| |
| failed: |
| close(fd); |
| |
| done: |
| g_string_free(content, TRUE); |
| umask(old_umask); |
| |
| return err; |
| } |
| |
| int __connman_resolvfile_append(int index, const char *domain, |
| const char *server) |
| { |
| struct resolvfile_entry *entry; |
| |
| DBG("index %d server %s", index, server); |
| |
| if (index < 0) |
| return -ENOENT; |
| |
| entry = g_try_new0(struct resolvfile_entry, 1); |
| if (!entry) |
| return -ENOMEM; |
| |
| entry->index = index; |
| entry->domain = g_strdup(domain); |
| entry->server = g_strdup(server); |
| |
| resolvfile_list = g_list_append(resolvfile_list, entry); |
| |
| return resolvfile_export(); |
| } |
| |
| int __connman_resolvfile_remove(int index, const char *domain, |
| const char *server) |
| { |
| GList *list, *matches = NULL; |
| |
| DBG("index %d server %s", index, server); |
| |
| for (list = resolvfile_list; list; list = g_list_next(list)) { |
| struct resolvfile_entry *entry = list->data; |
| |
| if (index >= 0 && entry->index != index) |
| continue; |
| |
| if (domain && g_strcmp0(entry->domain, domain) != 0) |
| continue; |
| |
| if (g_strcmp0(entry->server, server) != 0) |
| continue; |
| |
| matches = g_list_append(matches, entry); |
| } |
| |
| resolvfile_remove_entries(matches); |
| |
| return resolvfile_export(); |
| } |
| |
| static void append_fallback_nameservers(void) |
| { |
| GSList *list; |
| |
| for (list = entry_list; list; list = list->next) { |
| struct entry_data *entry = list->data; |
| |
| if (entry->index >= 0 && entry->server) |
| return; |
| } |
| |
| for (list = entry_list; list; list = list->next) { |
| struct entry_data *entry = list->data; |
| |
| if (entry->index != -1 || !entry->server) |
| continue; |
| |
| DBG("index %d server %s", entry->index, entry->server); |
| |
| if (dnsproxy_enabled) { |
| __connman_dnsproxy_append(entry->index, entry->domain, |
| entry->server); |
| } else { |
| __connman_resolvfile_append(entry->index, |
| entry->domain, entry->server); |
| } |
| } |
| } |
| |
| static void remove_fallback_nameservers(void) |
| { |
| GSList *list; |
| |
| for (list = entry_list; list; list = list->next) { |
| struct entry_data *entry = list->data; |
| |
| if (entry->index >= 0 || !entry->server) |
| continue; |
| |
| DBG("index %d server %s", entry->index, entry->server); |
| |
| if (dnsproxy_enabled) { |
| __connman_dnsproxy_remove(entry->index, entry->domain, |
| entry->server); |
| } else { |
| __connman_resolvfile_remove(entry->index, |
| entry->domain, entry->server); |
| } |
| } |
| } |
| |
| static void remove_entries(GSList *entries) |
| { |
| GSList *list; |
| |
| for (list = entries; list; list = list->next) { |
| struct entry_data *entry = list->data; |
| |
| entry_list = g_slist_remove(entry_list, entry); |
| |
| if (dnsproxy_enabled) { |
| __connman_dnsproxy_remove(entry->index, entry->domain, |
| entry->server); |
| } else { |
| __connman_resolvfile_remove(entry->index, entry->domain, |
| entry->server); |
| } |
| |
| if (entry->timeout) |
| g_source_remove(entry->timeout); |
| g_free(entry->server); |
| g_free(entry->domain); |
| g_free(entry); |
| } |
| |
| g_slist_free(entries); |
| |
| append_fallback_nameservers(); |
| } |
| |
| static gboolean resolver_expire_cb(gpointer user_data) |
| { |
| struct entry_data *entry = user_data; |
| GSList *list; |
| |
| DBG("index %d domain %s server %s", |
| entry->index, entry->domain, entry->server); |
| |
| list = g_slist_prepend(NULL, entry); |
| |
| if (entry->index >= 0) { |
| struct connman_service *service; |
| service = __connman_service_lookup_from_index(entry->index); |
| if (service) |
| __connman_service_nameserver_remove(service, |
| entry->server, true); |
| } |
| |
| remove_entries(list); |
| |
| return FALSE; |
| } |
| |
| static gboolean resolver_refresh_cb(gpointer user_data) |
| { |
| struct entry_data *entry = user_data; |
| unsigned int interval; |
| struct connman_service *service = NULL; |
| |
| /* Round up what we have left from lifetime */ |
| interval = entry->lifetime * |
| (1 - RESOLVER_LIFETIME_REFRESH_THRESHOLD) + 1.0; |
| |
| DBG("RDNSS start index %d domain %s " |
| "server %s remaining lifetime %d", |
| entry->index, entry->domain, |
| entry->server, interval); |
| |
| entry->timeout = g_timeout_add_seconds(interval, |
| resolver_expire_cb, entry); |
| |
| if (entry->index >= 0) { |
| service = __connman_service_lookup_from_index(entry->index); |
| if (service) { |
| /* |
| * Send Router Solicitation to refresh RDNSS entries |
| * before their lifetime expires |
| */ |
| __connman_network_refresh_rs_ipv6( |
| __connman_service_get_network(service), |
| entry->index); |
| } |
| } |
| return FALSE; |
| } |
| |
| static int append_resolver(int index, const char *domain, |
| const char *server, unsigned int lifetime, |
| unsigned int flags) |
| { |
| struct entry_data *entry; |
| unsigned int interval; |
| |
| DBG("index %d domain %s server %s lifetime %d flags %d", |
| index, domain, server, lifetime, flags); |
| |
| if (!server && !domain) |
| return -EINVAL; |
| |
| entry = g_try_new0(struct entry_data, 1); |
| if (!entry) |
| return -ENOMEM; |
| |
| entry->index = index; |
| entry->domain = g_strdup(domain); |
| entry->server = g_strdup(server); |
| entry->flags = flags; |
| entry->lifetime = lifetime; |
| |
| if (server) |
| entry->family = connman_inet_check_ipaddress(server); |
| |
| if (lifetime) { |
| interval = lifetime * RESOLVER_LIFETIME_REFRESH_THRESHOLD; |
| |
| DBG("RDNSS start index %d domain %s " |
| "server %s lifetime threshold %d", |
| index, domain, server, interval); |
| |
| entry->timeout = g_timeout_add_seconds(interval, |
| resolver_refresh_cb, entry); |
| |
| /* |
| * We update the service only for those nameservers |
| * that are automagically added via netlink (lifetime > 0) |
| */ |
| if (server && entry->index >= 0) { |
| struct connman_service *service; |
| service = __connman_service_lookup_from_index(entry->index); |
| if (service) |
| __connman_service_nameserver_append(service, |
| server, true); |
| } |
| } |
| |
| if (entry->index >= 0 && entry->server) |
| remove_fallback_nameservers(); |
| |
| entry_list = g_slist_append(entry_list, entry); |
| |
| if (dnsproxy_enabled) |
| __connman_dnsproxy_append(entry->index, domain, server); |
| else |
| __connman_resolvfile_append(entry->index, domain, server); |
| |
| return 0; |
| } |
| |
| /** |
| * connman_resolver_append: |
| * @index: network interface index |
| * @domain: domain limitation |
| * @server: server address |
| * |
| * Append resolver server address to current list |
| */ |
| int connman_resolver_append(int index, const char *domain, |
| const char *server) |
| { |
| GSList *list; |
| |
| DBG("index %d domain %s server %s", index, domain, server); |
| |
| if (!server && !domain) |
| return -EINVAL; |
| |
| for (list = entry_list; list; list = list->next) { |
| struct entry_data *entry = list->data; |
| |
| if (entry->timeout > 0) |
| continue; |
| |
| if (entry->index == index && |
| g_strcmp0(entry->domain, domain) == 0 && |
| g_strcmp0(entry->server, server) == 0) { |
| if (dnsproxy_enabled) |
| __connman_dnsproxy_append(entry->index, domain, |
| server); |
| |
| return -EEXIST; |
| } |
| } |
| |
| return append_resolver(index, domain, server, 0, 0); |
| } |
| |
| /** |
| * connman_resolver_append_lifetime: |
| * @index: network interface index |
| * @domain: domain limitation |
| * @server: server address |
| * @timeout: server lifetime in seconds |
| * |
| * Append resolver server address to current list |
| */ |
| int connman_resolver_append_lifetime(int index, const char *domain, |
| const char *server, unsigned int lifetime) |
| { |
| GSList *list; |
| unsigned int interval; |
| |
| DBG("index %d domain %s server %s lifetime %d", |
| index, domain, server, lifetime); |
| |
| if (!server && !domain) |
| return -EINVAL; |
| |
| for (list = entry_list; list; list = list->next) { |
| struct entry_data *entry = list->data; |
| |
| if (entry->timeout == 0 || |
| entry->index != index || |
| g_strcmp0(entry->domain, domain) != 0 || |
| g_strcmp0(entry->server, server) != 0) |
| continue; |
| |
| g_source_remove(entry->timeout); |
| |
| if (lifetime == 0) { |
| resolver_expire_cb(entry); |
| return 0; |
| } |
| |
| interval = lifetime * RESOLVER_LIFETIME_REFRESH_THRESHOLD; |
| |
| DBG("RDNSS start index %d domain %s " |
| "server %s lifetime threshold %d", |
| index, domain, server, interval); |
| |
| entry->timeout = g_timeout_add_seconds(interval, |
| resolver_refresh_cb, entry); |
| return 0; |
| } |
| |
| return append_resolver(index, domain, server, lifetime, 0); |
| } |
| |
| /** |
| * connman_resolver_remove: |
| * @index: network interface index |
| * @domain: domain limitation |
| * @server: server address |
| * |
| * Remover resolver server address from current list |
| */ |
| int connman_resolver_remove(int index, const char *domain, const char *server) |
| { |
| GSList *list, *matches = NULL; |
| |
| DBG("index %d domain %s server %s", index, domain, server); |
| |
| for (list = entry_list; list; list = list->next) { |
| struct entry_data *entry = list->data; |
| |
| if (entry->index != index) |
| continue; |
| |
| if (g_strcmp0(entry->domain, domain) != 0) |
| continue; |
| |
| if (g_strcmp0(entry->server, server) != 0) |
| continue; |
| |
| matches = g_slist_prepend(matches, entry); |
| break; |
| } |
| |
| if (!matches) |
| return -ENOENT; |
| |
| remove_entries(matches); |
| |
| return 0; |
| } |
| |
| /** |
| * connman_resolver_remove_all: |
| * @index: network interface index |
| * |
| * Remove all resolver server address for the specified interface index |
| */ |
| int connman_resolver_remove_all(int index) |
| { |
| GSList *list, *matches = NULL; |
| |
| DBG("index %d", index); |
| |
| if (index < 0) |
| return -EINVAL; |
| |
| for (list = entry_list; list; list = list->next) { |
| struct entry_data *entry = list->data; |
| |
| if (entry->index != index) |
| continue; |
| |
| matches = g_slist_prepend(matches, entry); |
| } |
| |
| if (!matches) |
| return -ENOENT; |
| |
| remove_entries(matches); |
| |
| return 0; |
| } |
| |
| int __connman_resolver_redo_servers(int index) |
| { |
| GSList *list; |
| |
| if (!dnsproxy_enabled) |
| return 0; |
| |
| DBG("index %d", index); |
| |
| if (index < 0) |
| return -EINVAL; |
| |
| for (list = entry_list; list; list = list->next) { |
| struct entry_data *entry = list->data; |
| |
| if (entry->timeout == 0 || entry->index != index) |
| continue; |
| |
| /* |
| * This function must only check IPv6 server addresses so |
| * do not remove IPv4 name servers unnecessarily. |
| */ |
| if (entry->family != AF_INET6) |
| continue; |
| |
| /* |
| * We remove the server, and then re-create so that it will |
| * use proper source addresses when sending DNS queries. |
| */ |
| __connman_dnsproxy_remove(entry->index, entry->domain, |
| entry->server); |
| |
| __connman_dnsproxy_append(entry->index, entry->domain, |
| entry->server); |
| } |
| |
| return 0; |
| } |
| |
| static void free_entry(gpointer data) |
| { |
| struct entry_data *entry = data; |
| g_free(entry->domain); |
| g_free(entry->server); |
| g_free(entry); |
| } |
| |
| static void free_resolvfile(gpointer data) |
| { |
| struct resolvfile_entry *entry = data; |
| g_free(entry->domain); |
| g_free(entry->server); |
| g_free(entry); |
| } |
| |
| int __connman_resolver_init(gboolean dnsproxy) |
| { |
| int i; |
| char **ns; |
| |
| DBG("dnsproxy %d", dnsproxy); |
| |
| if (!dnsproxy) |
| return 0; |
| |
| if (__connman_dnsproxy_init() < 0) { |
| /* Fall back to resolv.conf */ |
| return 0; |
| } |
| |
| dnsproxy_enabled = true; |
| |
| ns = connman_setting_get_string_list("FallbackNameservers"); |
| for (i = 0; ns && ns[i]; i += 1) { |
| DBG("server %s", ns[i]); |
| append_resolver(-1, NULL, ns[i], 0, RESOLVER_FLAG_PUBLIC); |
| } |
| |
| return 0; |
| } |
| |
| void __connman_resolver_cleanup(void) |
| { |
| DBG(""); |
| |
| if (dnsproxy_enabled) |
| __connman_dnsproxy_cleanup(); |
| else { |
| GList *list; |
| GSList *slist; |
| |
| for (list = resolvfile_list; list; list = g_list_next(list)) |
| free_resolvfile(list->data); |
| g_list_free(resolvfile_list); |
| resolvfile_list = NULL; |
| |
| for (slist = entry_list; slist; slist = g_slist_next(slist)) |
| free_entry(slist->data); |
| g_slist_free(entry_list); |
| entry_list = NULL; |
| } |
| } |