diff options
author | Zhang zhengguang <zhengguang.zhang@intel.com> | 2014-07-17 10:37:39 +0800 |
---|---|---|
committer | Zhang zhengguang <zhengguang.zhang@intel.com> | 2014-07-17 10:37:39 +0800 |
commit | 1b9d0a62f59bb48c8deb2f0b98d9acdffdd9abe7 (patch) | |
tree | 6e991827d28537f7f40f20786c2354fd04a9fdad /src/ipv6pd.c | |
parent | fbe905ab58ecc31fe64c410c5f580cadc30e7f04 (diff) | |
download | connman-1b9d0a62f59bb48c8deb2f0b98d9acdffdd9abe7.tar.gz connman-1b9d0a62f59bb48c8deb2f0b98d9acdffdd9abe7.tar.bz2 connman-1b9d0a62f59bb48c8deb2f0b98d9acdffdd9abe7.zip |
Imported Upstream version 1.24upstream/1.24
Diffstat (limited to 'src/ipv6pd.c')
-rw-r--r-- | src/ipv6pd.c | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/src/ipv6pd.c b/src/ipv6pd.c new file mode 100644 index 00000000..5ecda389 --- /dev/null +++ b/src/ipv6pd.c @@ -0,0 +1,383 @@ +/* + * + * Connection Manager + * + * Copyright (C) 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 + +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdio.h> + +#include "connman.h" + +#include <gdhcp/gdhcp.h> + +#define DEFAULT_ROUTER_LIFETIME 180 /* secs */ +#define DEFAULT_RA_INTERVAL 120 /* secs */ + +static int bridge_index = -1; +static guint timer_uplink; +static guint timer_ra; +static char *default_interface; +static GSList *prefixes; +static GHashTable *timer_hash; +static void *rs_context; + +static int setup_prefix_delegation(struct connman_service *service); +static void dhcpv6_callback(struct connman_network *network, + enum __connman_dhcpv6_status status, gpointer data); + +static int enable_ipv6_forward(bool enable) +{ + FILE *f; + + f = fopen("/proc/sys/net/ipv6/ip_forward", "r+"); + if (!f) + return -errno; + + if (enable) + fprintf(f, "1"); + else + fprintf(f, "0"); + + fclose(f); + + return 0; +} + +static gboolean send_ra(gpointer data) +{ + __connman_inet_ipv6_send_ra(bridge_index, NULL, prefixes, + DEFAULT_ROUTER_LIFETIME); + + return TRUE; +} + +static void start_ra(int ifindex, GSList *prefix) +{ + if (prefixes) + g_slist_free_full(prefixes, g_free); + + prefixes = g_dhcpv6_copy_prefixes(prefix); + + enable_ipv6_forward(true); + + if (timer_ra > 0) + g_source_remove(timer_ra); + + send_ra(NULL); + + timer_ra = g_timeout_add_seconds(DEFAULT_RA_INTERVAL, send_ra, NULL); +} + +static void stop_ra(int ifindex) +{ + __connman_inet_ipv6_send_ra(ifindex, NULL, prefixes, 0); + + if (timer_ra > 0) { + g_source_remove(timer_ra); + timer_ra = 0; + } + + enable_ipv6_forward(false); + + if (prefixes) { + g_slist_free_full(prefixes, g_free); + prefixes = NULL; + } +} + +static void rs_received(struct nd_router_solicit *reply, + unsigned int length, void *user_data) +{ + GDHCPIAPrefix *prefix; + GSList *list; + + if (!prefixes) + return; + + DBG(""); + + for (list = prefixes; list; list = list->next) { + prefix = list->data; + + prefix->valid -= time(NULL) - prefix->expire; + prefix->preferred -= time(NULL) - prefix->expire; + } + + __connman_inet_ipv6_send_ra(bridge_index, NULL, prefixes, + DEFAULT_ROUTER_LIFETIME); +} + +static gboolean do_setup(gpointer data) +{ + int ret; + + timer_uplink = 0; + + if (!default_interface) + DBG("No uplink connection, retrying prefix delegation"); + + ret = setup_prefix_delegation(__connman_service_get_default()); + if (ret < 0 && ret != -EINPROGRESS) + return TRUE; /* delegation error, try again */ + + return FALSE; +} + +static void dhcpv6_renew_callback(struct connman_network *network, + enum __connman_dhcpv6_status status, + gpointer data) +{ + DBG("network %p status %d data %p", network, status, data); + + if (status == CONNMAN_DHCPV6_STATUS_SUCCEED) + dhcpv6_callback(network, status, data); + else + setup_prefix_delegation(__connman_service_get_default()); +} + +static void cleanup(void) +{ + if (timer_uplink != 0) { + g_source_remove(timer_uplink); + timer_uplink = 0; + } + + g_hash_table_destroy(timer_hash); + timer_hash = NULL; + + if (prefixes) { + g_slist_free_full(prefixes, g_free); + prefixes = NULL; + } +} + +static void dhcpv6_callback(struct connman_network *network, + enum __connman_dhcpv6_status status, gpointer data) +{ + GSList *prefix_list = data; + + DBG("network %p status %d data %p", network, status, data); + + if (status == CONNMAN_DHCPV6_STATUS_FAIL) { + DBG("Prefix delegation request failed"); + cleanup(); + return; + } + + if (!prefix_list) { + DBG("No prefixes, retrying"); + if (timer_uplink == 0) + timer_uplink = g_timeout_add_seconds(10, do_setup, + NULL); + return; + } + + /* + * After we have got a list of prefixes, we can start to send router + * advertisements (RA) to tethering interface. + */ + start_ra(bridge_index, prefix_list); + + if (__connman_dhcpv6_start_pd_renew(network, + dhcpv6_renew_callback) == -ETIMEDOUT) + dhcpv6_renew_callback(network, CONNMAN_DHCPV6_STATUS_FAIL, + NULL); +} + +static int setup_prefix_delegation(struct connman_service *service) +{ + struct connman_ipconfig *ipconfig; + char *interface; + int err = 0, ifindex; + + if (!service) { + /* + * We do not have uplink connection. We just wait until + * default interface is updated. + */ + return -EINPROGRESS; + } + + interface = connman_service_get_interface(service); + + DBG("interface %s bridge_index %d", interface, bridge_index); + + if (default_interface) { + stop_ra(bridge_index); + + ifindex = connman_inet_ifindex(default_interface); + __connman_dhcpv6_stop_pd(ifindex); + } + + g_free(default_interface); + + ipconfig = __connman_service_get_ip6config(service); + if (!__connman_ipconfig_ipv6_is_enabled(ipconfig)) { + g_free(interface); + default_interface = NULL; + return -EPFNOSUPPORT; + } + + default_interface = interface; + + if (default_interface) { + ifindex = connman_inet_ifindex(default_interface); + + /* + * Try to get a IPv6 prefix so we can start to advertise it. + */ + err = __connman_dhcpv6_start_pd(ifindex, prefixes, + dhcpv6_callback); + if (err < 0) + DBG("prefix delegation %d/%s", err, strerror(-err)); + } + + return err; +} + +static void cleanup_timer(gpointer user_data) +{ + guint timer = GPOINTER_TO_UINT(user_data); + + g_source_remove(timer); +} + +static void update_default_interface(struct connman_service *service) +{ + setup_prefix_delegation(service); +} + +static void update_ipconfig(struct connman_service *service, + struct connman_ipconfig *ipconfig) +{ + if (!service || service != __connman_service_get_default()) + return; + + if (ipconfig != __connman_service_get_ip6config(service)) + return; + + if (!__connman_ipconfig_ipv6_is_enabled(ipconfig)) { + if (default_interface) { + int ifindex; + + ifindex = connman_inet_ifindex(default_interface); + __connman_dhcpv6_stop_pd(ifindex); + + g_free(default_interface); + default_interface = NULL; + } + + DBG("No IPv6 support for interface index %d", + __connman_ipconfig_get_index(ipconfig)); + return; + } + + /* + * Did we had PD activated already? If not, then start it. + */ + if (!default_interface) { + DBG("IPv6 ipconfig %p changed for interface index %d", ipconfig, + __connman_ipconfig_get_index(ipconfig)); + + setup_prefix_delegation(service); + } +} + +static struct connman_notifier pd_notifier = { + .name = "IPv6 prefix delegation", + .default_changed = update_default_interface, + .ipconfig_changed = update_ipconfig, +}; + +int __connman_ipv6pd_setup(const char *bridge) +{ + struct connman_service *service; + int err; + + if (!connman_inet_is_ipv6_supported()) + return -EPFNOSUPPORT; + + if (bridge_index >= 0) { + DBG("Prefix delegation already running"); + return -EALREADY; + } + + err = connman_notifier_register(&pd_notifier); + if (err < 0) + return err; + + bridge_index = connman_inet_ifindex(bridge); + + timer_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, cleanup_timer); + + err = __connman_inet_ipv6_start_recv_rs(bridge_index, rs_received, + NULL, &rs_context); + if (err < 0) + DBG("Cannot receive router solicitation %d/%s", + err, strerror(-err)); + + service = __connman_service_get_default(); + if (service) { + /* + * We have an uplink connection already, try to use it. + */ + return setup_prefix_delegation(service); + } + + /* + * The prefix delegation is started after have got the uplink + * connection up i.e., when the default service is setup in which + * case the update_default_interface() will be called. + */ + return -EINPROGRESS; +} + +void __connman_ipv6pd_cleanup(void) +{ + int ifindex; + + if (!connman_inet_is_ipv6_supported()) + return; + + connman_notifier_unregister(&pd_notifier); + + __connman_inet_ipv6_stop_recv_rs(rs_context); + rs_context = NULL; + + cleanup(); + + stop_ra(bridge_index); + + if (default_interface) { + ifindex = connman_inet_ifindex(default_interface); + __connman_dhcpv6_stop_pd(ifindex); + g_free(default_interface); + default_interface = NULL; + } + + bridge_index = -1; +} |