/* * * 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 #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #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; #ifdef TIZEN_EXT if (g_strcmp0(server, "0.0.0.0") == 0) return -EINVAL; #endif 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; } }