| /* |
| * |
| * Connection Manager |
| * |
| * Copyright (C) 2007-2017 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 |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <gdbus.h> |
| #include <glib.h> |
| #include <connman/dbus.h> |
| |
| #include "connman.h" |
| |
| #define SYSTEMD_RESOLVED_SERVICE "org.freedesktop.resolve1" |
| #define SYSTEMD_RESOLVED_PATH "/org/freedesktop/resolve1" |
| |
| struct mdns_data { |
| int index; |
| bool enabled; |
| }; |
| |
| static GHashTable *interface_hash; |
| static DBusConnection *connection; |
| static GDBusClient *client; |
| static GDBusProxy *resolved_proxy; |
| |
| /* update after a full set of instructions has been received */ |
| static guint update_interfaces_source; |
| |
| struct dns_interface { |
| GList *domains; |
| GList *servers; |
| int index; |
| bool needs_domain_update; |
| bool needs_server_update; |
| }; |
| |
| static gboolean compare_index(gconstpointer a, gconstpointer b) |
| { |
| gint ai = GPOINTER_TO_UINT(a); |
| gint bi = GPOINTER_TO_UINT(b); |
| |
| return ai == bi; |
| } |
| |
| static void free_dns_interface(gpointer data) |
| { |
| struct dns_interface *iface = data; |
| |
| if (!iface) |
| return; |
| |
| g_list_free_full(iface->domains, g_free); |
| g_list_free_full(iface->servers, g_free); |
| |
| g_free(iface); |
| } |
| |
| static void setlinkdns_append(DBusMessageIter *iter, void *user_data) |
| { |
| struct dns_interface *iface = user_data; |
| int result; |
| unsigned int i; |
| int type; |
| char ipv4_bytes[4]; |
| char ipv6_bytes[16]; |
| GList *list; |
| DBusMessageIter address_array, struct_array, byte_array; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &iface->index); |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "(iay)", |
| &address_array); |
| |
| for (list = iface->servers; list; list = g_list_next(list)) { |
| char *server = list->data; |
| |
| DBG("index: %d, server: %s", iface->index, server); |
| |
| dbus_message_iter_open_container(&address_array, |
| DBUS_TYPE_STRUCT, NULL, &struct_array); |
| |
| type = connman_inet_check_ipaddress(server); |
| |
| if (type == AF_INET) { |
| result = inet_pton(type, server, ipv4_bytes); |
| if (result != 1) { |
| DBG("Failed to parse IPv4 address: %s", |
| server); |
| return; |
| } |
| |
| dbus_message_iter_append_basic(&struct_array, |
| DBUS_TYPE_INT32, &type); |
| |
| dbus_message_iter_open_container(&struct_array, |
| DBUS_TYPE_ARRAY, "y", &byte_array); |
| |
| for (i = 0; i < sizeof(ipv4_bytes); i++) { |
| dbus_message_iter_append_basic(&byte_array, |
| DBUS_TYPE_BYTE, |
| &(ipv4_bytes[i])); |
| } |
| |
| dbus_message_iter_close_container(&struct_array, |
| &byte_array); |
| } else if (type == AF_INET6) { |
| result = inet_pton(type, server, ipv6_bytes); |
| if (result != 1) { |
| DBG("Failed to parse IPv6 address: %s", server); |
| return; |
| } |
| |
| dbus_message_iter_append_basic(&struct_array, |
| DBUS_TYPE_INT32, &type); |
| |
| dbus_message_iter_open_container(&struct_array, |
| DBUS_TYPE_ARRAY, "y", &byte_array); |
| |
| for (i = 0; i < sizeof(ipv6_bytes); i++) { |
| dbus_message_iter_append_basic(&byte_array, |
| DBUS_TYPE_BYTE, |
| &(ipv6_bytes[i])); |
| } |
| |
| dbus_message_iter_close_container(&struct_array, |
| &byte_array); |
| } |
| |
| dbus_message_iter_close_container(&address_array, |
| &struct_array); |
| } |
| |
| dbus_message_iter_close_container(iter, &address_array); |
| } |
| |
| static void setlinkdomains_append(DBusMessageIter *iter, void *user_data) |
| { |
| struct dns_interface *iface = user_data; |
| GList *list; |
| DBusMessageIter domain_array, struct_array; |
| gboolean only_routing = FALSE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &iface->index); |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "(sb)", |
| &domain_array); |
| |
| for (list = iface->domains; list; list = g_list_next(list)) { |
| char *domain = list->data; |
| |
| DBG("index: %d, domain: %s", iface->index, domain); |
| |
| dbus_message_iter_open_container(&domain_array, |
| DBUS_TYPE_STRUCT, NULL, &struct_array); |
| |
| dbus_message_iter_append_basic(&struct_array, DBUS_TYPE_STRING, |
| &domain); |
| |
| dbus_message_iter_append_basic(&struct_array, DBUS_TYPE_BOOLEAN, |
| &only_routing); |
| |
| dbus_message_iter_close_container(&domain_array, &struct_array); |
| } |
| |
| dbus_message_iter_close_container(iter, &domain_array); |
| } |
| |
| static int set_systemd_resolved_values(struct dns_interface *iface) |
| { |
| if (!resolved_proxy || !iface) |
| return -ENOENT; |
| |
| /* No async error processing -- just fire and forget */ |
| |
| if (iface->needs_server_update) { |
| if (!g_dbus_proxy_method_call(resolved_proxy, "SetLinkDNS", |
| setlinkdns_append, NULL, iface, NULL)) |
| return -EINVAL; |
| |
| iface->needs_server_update = FALSE; |
| } |
| |
| if (iface->needs_domain_update) { |
| if (!g_dbus_proxy_method_call(resolved_proxy, "SetLinkDomains", |
| setlinkdomains_append, NULL, iface, NULL)) |
| return -EINVAL; |
| |
| iface->needs_domain_update = FALSE; |
| } |
| |
| return 0; |
| } |
| |
| static bool is_empty(struct dns_interface *iface) |
| { |
| if (!iface) |
| return FALSE; |
| |
| return (!iface->domains && !iface->servers); |
| } |
| |
| static void update_interface(gpointer key, gpointer value, gpointer data) |
| { |
| struct dns_interface *iface = value; |
| GList **removed_items = data; |
| |
| set_systemd_resolved_values(iface); |
| |
| if (is_empty(iface)) |
| *removed_items = g_list_prepend(*removed_items, iface); |
| } |
| |
| static int update_systemd_resolved(gpointer data) |
| { |
| GList *removed_items = NULL, *list; |
| |
| if (!interface_hash) { |
| DBG("no interface hash when updating"); |
| |
| return G_SOURCE_REMOVE; |
| } |
| |
| g_hash_table_foreach(interface_hash, update_interface, &removed_items); |
| |
| for (list = removed_items; list; list = g_list_next(list)) { |
| struct dns_interface *iface = list->data; |
| |
| g_hash_table_remove(interface_hash, |
| GUINT_TO_POINTER(iface->index)); |
| } |
| |
| g_list_free(removed_items); |
| |
| update_interfaces_source = 0; |
| |
| return G_SOURCE_REMOVE; |
| } |
| |
| static GList *remove_string(GList *str_list, const char *str) |
| { |
| GList *match = NULL; |
| |
| match = g_list_find_custom(str_list, str, |
| (GCompareFunc) g_strcmp0); |
| if (match) { |
| g_free(match->data); |
| return g_list_delete_link(str_list, match); |
| } |
| |
| return str_list; |
| } |
| |
| static void remove_values(struct dns_interface *iface, const char *domain, |
| const char *server) |
| { |
| if (!iface) |
| return; |
| |
| if (domain) { |
| iface->domains = remove_string(iface->domains, domain); |
| iface->needs_domain_update = TRUE; |
| } |
| |
| if (server) { |
| iface->servers = remove_string(iface->servers, server); |
| iface->needs_server_update = TRUE; |
| } |
| } |
| |
| int __connman_dnsproxy_remove(int index, const char *domain, |
| const char *server) |
| { |
| struct dns_interface *iface; |
| |
| DBG("%d, %s, %s", index, domain ? domain : "no domain", |
| server ? server : "no server"); |
| |
| if (!interface_hash || index < 0) |
| return -EINVAL; |
| |
| iface = g_hash_table_lookup(interface_hash, GUINT_TO_POINTER(index)); |
| |
| if (!iface) |
| return -EINVAL; |
| |
| remove_values(iface, domain, server); |
| |
| if (!update_interfaces_source) |
| update_interfaces_source = g_idle_add(update_systemd_resolved, |
| NULL); |
| |
| return 0; |
| } |
| |
| static GList *replace_to_end(GList *str_list, const char *str) |
| { |
| GList *list; |
| |
| for (list = str_list; list; list = g_list_next(list)) { |
| char *orig = list->data; |
| |
| if (g_strcmp0(orig, str) == 0) { |
| str_list = g_list_remove(str_list, orig); |
| g_free(orig); |
| break; |
| } |
| } |
| |
| return g_list_append(str_list, g_strdup(str)); |
| } |
| |
| int __connman_dnsproxy_append(int index, const char *domain, |
| const char *server) |
| { |
| struct dns_interface *iface; |
| |
| DBG("%d, %s, %s", index, domain ? domain : "no domain", |
| server ? server : "no server"); |
| |
| if (!interface_hash || index < 0) |
| return -EINVAL; |
| |
| iface = g_hash_table_lookup(interface_hash, GUINT_TO_POINTER(index)); |
| |
| if (!iface) { |
| iface = g_new0(struct dns_interface, 1); |
| if (!iface) |
| return -ENOMEM; |
| |
| iface->index = index; |
| g_hash_table_replace(interface_hash, GUINT_TO_POINTER(index), iface); |
| } |
| |
| if (domain) { |
| iface->domains = replace_to_end(iface->domains, domain); |
| iface->needs_domain_update = TRUE; |
| } |
| |
| if (server) { |
| iface->servers = replace_to_end(iface->servers, server); |
| iface->needs_server_update = TRUE; |
| } |
| |
| if (!update_interfaces_source) |
| update_interfaces_source = g_idle_add(update_systemd_resolved, |
| NULL); |
| |
| return 0; |
| } |
| |
| int __connman_dnsproxy_add_listener(int index) |
| { |
| DBG(""); |
| |
| return -ENXIO; |
| } |
| |
| void __connman_dnsproxy_remove_listener(int index) |
| { |
| DBG(""); |
| } |
| |
| static int setup_resolved(void) |
| { |
| connection = connman_dbus_get_connection(); |
| if (!connection) |
| return -ENXIO; |
| |
| client = g_dbus_client_new(connection, SYSTEMD_RESOLVED_SERVICE, |
| SYSTEMD_RESOLVED_PATH); |
| |
| if (!client) |
| return -EINVAL; |
| |
| resolved_proxy = g_dbus_proxy_new(client, SYSTEMD_RESOLVED_PATH, |
| "org.freedesktop.resolve1.Manager"); |
| |
| if (!resolved_proxy) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static void setlinkmulticastdns_append(DBusMessageIter *iter, void *user_data) { |
| struct mdns_data *data = user_data; |
| char *val = "no"; |
| |
| if (data->enabled) |
| val = "yes"; |
| |
| DBG("SetLinkMulticastDNS: %d/%s", data->index, val); |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &data->index); |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &val); |
| } |
| |
| int __connman_dnsproxy_set_mdns(int index, bool enabled) |
| { |
| struct mdns_data data = { .index = index, .enabled = enabled }; |
| |
| if (!resolved_proxy) |
| return -ENOENT; |
| |
| if (index < 0) |
| return -EINVAL; |
| |
| if (!g_dbus_proxy_method_call(resolved_proxy, "SetLinkMulticastDNS", |
| setlinkmulticastdns_append, NULL, &data, NULL)) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| int __connman_dnsproxy_init(void) |
| { |
| int ret; |
| |
| DBG(""); |
| |
| ret = setup_resolved(); |
| if (ret) |
| return ret; |
| |
| interface_hash = g_hash_table_new_full(g_direct_hash, |
| compare_index, |
| NULL, |
| free_dns_interface); |
| if (!interface_hash) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| void __connman_dnsproxy_cleanup(void) |
| { |
| DBG(""); |
| |
| if (update_interfaces_source) { |
| /* |
| * It might be that we don't get to an idle loop anymore, so |
| * run the update function once more to clean up. |
| */ |
| g_source_remove(update_interfaces_source); |
| update_systemd_resolved(NULL); |
| update_interfaces_source = 0; |
| } |
| |
| if (interface_hash) { |
| g_hash_table_destroy(interface_hash); |
| interface_hash = NULL; |
| } |
| |
| if (resolved_proxy) { |
| g_dbus_proxy_unref(resolved_proxy); |
| resolved_proxy = NULL; |
| } |
| |
| if (client) { |
| g_dbus_client_unref(client); |
| client = NULL; |
| } |
| |
| if (connection) { |
| dbus_connection_unref(connection); |
| connection = NULL; |
| } |
| } |