*
* Connection Manager
*
- * Copyright (C) 2007-2009 Intel Corporation. All rights reserved.
+ * Copyright (C) 2007-2013 Intel Corporation. All rights reserved.
+ * Copyright (C) 2011-2014 BMW Car IT GmbH.
*
* 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
#endif
#include <errno.h>
-#include <unistd.h>
#include <string.h>
-#include <sys/ioctl.h>
-#include <arpa/inet.h>
#include <net/if.h>
-#include <net/route.h>
#include <gdbus.h>
#include "connman.h"
+struct gateway_config {
+ bool active;
+ char *gateway;
+
+ /* VPN extra data */
+ bool vpn;
+ char *vpn_ip;
+ int vpn_phy_index;
+ char *vpn_phy_ip;
+};
+
struct gateway_data {
int index;
- char *gateway;
+ struct connman_service *service;
+ struct gateway_config *ipv4_gateway;
+ struct gateway_config *ipv6_gateway;
+ bool default_checked;
};
-static GSList *gateway_list = NULL;
+static GHashTable *gateway_hash = NULL;
-static struct gateway_data *find_gateway(int index, const char *gateway)
+static struct gateway_config *find_gateway(int index, const char *gateway)
{
- GSList *list;
+ GHashTableIter iter;
+ gpointer value, key;
- if (gateway == NULL)
+ if (!gateway)
return NULL;
- for (list = gateway_list; list; list = list->next) {
- struct gateway_data *data = list->data;
+ g_hash_table_iter_init(&iter, gateway_hash);
- if (data->gateway == NULL)
- continue;
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ struct gateway_data *data = value;
+
+ if (data->ipv4_gateway && data->index == index &&
+ g_str_equal(data->ipv4_gateway->gateway,
+ gateway))
+ return data->ipv4_gateway;
+
+ if (data->ipv6_gateway && data->index == index &&
+ g_str_equal(data->ipv6_gateway->gateway,
+ gateway))
+ return data->ipv6_gateway;
+ }
+
+ return NULL;
+}
+
+static struct gateway_data *lookup_gateway_data(struct gateway_config *config)
+{
+ GHashTableIter iter;
+ gpointer value, key;
- if (data->index == index &&
- g_str_equal(data->gateway, gateway) == TRUE)
+ if (!config)
+ return NULL;
+
+ g_hash_table_iter_init(&iter, gateway_hash);
+
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ struct gateway_data *data = value;
+
+ if (data->ipv4_gateway &&
+ data->ipv4_gateway == config)
+ return data;
+
+ if (data->ipv6_gateway &&
+ data->ipv6_gateway == config)
return data;
}
return NULL;
}
-static void connection_newgateway(int index, const char *gateway)
+static struct gateway_data *find_vpn_gateway(int index, const char *gateway)
+{
+ GHashTableIter iter;
+ gpointer value, key;
+
+ if (!gateway)
+ return NULL;
+
+ g_hash_table_iter_init(&iter, gateway_hash);
+
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ struct gateway_data *data = value;
+
+ if (data->ipv4_gateway && data->index == index &&
+ g_str_equal(data->ipv4_gateway->gateway,
+ gateway))
+ return data;
+
+ if (data->ipv6_gateway && data->index == index &&
+ g_str_equal(data->ipv6_gateway->gateway,
+ gateway))
+ return data;
+ }
+
+ return NULL;
+}
+
+struct get_gateway_params {
+ char *vpn_gateway;
+ int vpn_index;
+};
+
+static void get_gateway_cb(const char *gateway, int index, void *user_data)
{
+ struct gateway_config *config;
struct gateway_data *data;
+ struct get_gateway_params *params = user_data;
+ int family;
- DBG("index %d gateway %s", index, gateway);
+ if (index < 0)
+ goto out;
+
+ DBG("phy index %d phy gw %s vpn index %d vpn gw %s", index, gateway,
+ params->vpn_index, params->vpn_gateway);
+
+ data = find_vpn_gateway(params->vpn_index, params->vpn_gateway);
+ if (!data) {
+ DBG("Cannot find VPN link route, index %d addr %s",
+ params->vpn_index, params->vpn_gateway);
+ goto out;
+ }
+
+ family = connman_inet_check_ipaddress(params->vpn_gateway);
- data = find_gateway(index, gateway);
- if (data != NULL)
+ if (family == AF_INET)
+ config = data->ipv4_gateway;
+ else if (family == AF_INET6)
+ config = data->ipv6_gateway;
+ else
+ goto out;
+
+ config->vpn_phy_index = index;
+
+ DBG("vpn %s phy index %d", config->vpn_ip, config->vpn_phy_index);
+
+out:
+ g_free(params->vpn_gateway);
+ g_free(params);
+}
+
+static void set_vpn_routes(struct gateway_data *new_gateway,
+ struct connman_service *service,
+ const char *gateway,
+ enum connman_ipconfig_type type,
+ const char *peer,
+ struct gateway_data *active_gateway)
+{
+ struct gateway_config *config;
+ struct connman_ipconfig *ipconfig;
+ char *dest;
+
+ DBG("new %p service %p gw %s type %d peer %s active %p",
+ new_gateway, service, gateway, type, peer, active_gateway);
+
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV4) {
+ ipconfig = __connman_service_get_ip4config(service);
+ config = new_gateway->ipv4_gateway;
+ } else if (type == CONNMAN_IPCONFIG_TYPE_IPV6) {
+ ipconfig = __connman_service_get_ip6config(service);
+ config = new_gateway->ipv6_gateway;
+ } else
return;
- data = g_try_new0(struct gateway_data, 1);
- if (data == NULL)
+ if (config) {
+ int index = __connman_ipconfig_get_index(ipconfig);
+ struct get_gateway_params *params;
+
+ config->vpn = true;
+ if (peer)
+ config->vpn_ip = g_strdup(peer);
+ else if (gateway)
+ config->vpn_ip = g_strdup(gateway);
+
+ params = g_try_malloc(sizeof(struct get_gateway_params));
+ if (!params)
+ return;
+
+ params->vpn_index = index;
+ params->vpn_gateway = g_strdup(gateway);
+
+ /*
+ * Find the gateway that is serving the VPN link
+ */
+ __connman_inet_get_route(gateway, get_gateway_cb, params);
+ }
+
+ if (!active_gateway)
return;
- data->index = index;
- data->gateway = g_strdup(gateway);
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV4) {
+ /*
+ * Special route to VPN server via gateway. This
+ * is needed so that we can access hosts behind
+ * the VPN. The route might already exist depending
+ * on network topology.
+ */
+ if (!active_gateway->ipv4_gateway)
+ return;
- gateway_list = g_slist_append(gateway_list, data);
+
+ /*
+ * If VPN server is on same subnet as we are, skip adding
+ * route.
+ */
+ if (connman_inet_compare_subnet(active_gateway->index,
+ gateway))
+ return;
+
+ DBG("active gw %s", active_gateway->ipv4_gateway->gateway);
+
+ if (g_strcmp0(active_gateway->ipv4_gateway->gateway,
+ "0.0.0.0") != 0)
+ dest = active_gateway->ipv4_gateway->gateway;
+ else
+ dest = NULL;
+
+ connman_inet_add_host_route(active_gateway->index, gateway,
+ dest);
+
+ } else if (type == CONNMAN_IPCONFIG_TYPE_IPV6) {
+
+ if (!active_gateway->ipv6_gateway)
+ return;
+
+ if (connman_inet_compare_ipv6_subnet(active_gateway->index,
+ gateway))
+ return;
+
+ DBG("active gw %s", active_gateway->ipv6_gateway->gateway);
+
+ if (g_strcmp0(active_gateway->ipv6_gateway->gateway,
+ "::") != 0)
+ dest = active_gateway->ipv6_gateway->gateway;
+ else
+ dest = NULL;
+
+ connman_inet_add_ipv6_host_route(active_gateway->index,
+ gateway, dest);
+ }
}
-static void connection_delgateway(int index, const char *gateway)
+static int del_routes(struct gateway_data *data,
+ enum connman_ipconfig_type type)
{
- struct gateway_data *data;
+ int status4 = 0, status6 = 0;
+ bool do_ipv4 = false, do_ipv6 = false;
+
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
+ do_ipv4 = true;
+ else if (type == CONNMAN_IPCONFIG_TYPE_IPV6)
+ do_ipv6 = true;
+ else
+ do_ipv4 = do_ipv6 = true;
+
+ if (do_ipv4 && data->ipv4_gateway) {
+ if (data->ipv4_gateway->vpn) {
+ status4 = connman_inet_clear_gateway_address(
+ data->index,
+ data->ipv4_gateway->vpn_ip);
+
+ } else if (g_strcmp0(data->ipv4_gateway->gateway,
+ "0.0.0.0") == 0) {
+ status4 = connman_inet_clear_gateway_interface(
+ data->index);
+ } else {
+ connman_inet_del_host_route(data->index,
+ data->ipv4_gateway->gateway);
+ status4 = connman_inet_clear_gateway_address(
+ data->index,
+ data->ipv4_gateway->gateway);
+ }
+ }
- DBG("index %d gateway %s", index, gateway);
+ if (do_ipv6 && data->ipv6_gateway) {
+ if (data->ipv6_gateway->vpn) {
+ status6 = connman_inet_clear_ipv6_gateway_address(
+ data->index,
+ data->ipv6_gateway->vpn_ip);
+
+ } else if (g_strcmp0(data->ipv6_gateway->gateway, "::") == 0) {
+ status6 = connman_inet_clear_ipv6_gateway_interface(
+ data->index);
+ } else {
+ connman_inet_del_ipv6_host_route(data->index,
+ data->ipv6_gateway->gateway);
+ status6 = connman_inet_clear_ipv6_gateway_address(
+ data->index,
+ data->ipv6_gateway->gateway);
+ }
+ }
- data = find_gateway(index, gateway);
- if (data == NULL)
- return;
+ return (status4 < 0 ? status4 : status6);
+}
- gateway_list = g_slist_remove(gateway_list, data);
+static int disable_gateway(struct gateway_data *data,
+ enum connman_ipconfig_type type)
+{
+ bool active = false;
- g_free(data->gateway);
- g_free(data);
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV4) {
+ if (data->ipv4_gateway)
+ active = data->ipv4_gateway->active;
+ } else if (type == CONNMAN_IPCONFIG_TYPE_IPV6) {
+ if (data->ipv6_gateway)
+ active = data->ipv6_gateway->active;
+ } else
+ active = true;
+
+ DBG("type %d active %d", type, active);
+
+ if (active)
+ return del_routes(data, type);
+
+ return 0;
}
-static struct connman_rtnl connection_rtnl = {
- .name = "connection",
- .newgateway = connection_newgateway,
- .delgateway = connection_delgateway,
-};
+static struct gateway_data *add_gateway(struct connman_service *service,
+ int index, const char *gateway,
+ enum connman_ipconfig_type type)
+{
+ struct gateway_data *data, *old;
+ struct gateway_config *config;
+
+ if (!gateway || strlen(gateway) == 0)
+ return NULL;
+
+ data = g_try_new0(struct gateway_data, 1);
+ if (!data)
+ return NULL;
+
+ data->index = index;
+
+ config = g_try_new0(struct gateway_config, 1);
+ if (!config) {
+ g_free(data);
+ return NULL;
+ }
+
+ config->gateway = g_strdup(gateway);
+ config->vpn_ip = NULL;
+ config->vpn_phy_ip = NULL;
+ config->vpn = false;
+ config->vpn_phy_index = -1;
+ config->active = false;
+
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
+ data->ipv4_gateway = config;
+ else if (type == CONNMAN_IPCONFIG_TYPE_IPV6)
+ data->ipv6_gateway = config;
+ else {
+ g_free(config->gateway);
+ g_free(config);
+ g_free(data);
+ return NULL;
+ }
+
+ data->service = service;
+
+ /*
+ * If the service is already in the hash, then we
+ * must not replace it blindly but disable the gateway
+ * of the type we are replacing and take the other type
+ * from old gateway settings.
+ */
+ old = g_hash_table_lookup(gateway_hash, service);
+ if (old) {
+ DBG("Replacing gw %p ipv4 %p ipv6 %p", old,
+ old->ipv4_gateway, old->ipv6_gateway);
+ disable_gateway(old, type);
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV4) {
+ data->ipv6_gateway = old->ipv6_gateway;
+ old->ipv6_gateway = NULL;
+ } else if (type == CONNMAN_IPCONFIG_TYPE_IPV6) {
+ data->ipv4_gateway = old->ipv4_gateway;
+ old->ipv4_gateway = NULL;
+ }
+ }
+
+ connman_service_ref(data->service);
+ g_hash_table_replace(gateway_hash, service, data);
+
+ return data;
+}
-static int set_route(struct connman_element *element, const char *gateway)
+static void set_default_gateway(struct gateway_data *data,
+ enum connman_ipconfig_type type)
{
- struct ifreq ifr;
- struct rtentry rt;
- struct sockaddr_in *addr;
- int sk, err;
+ int index;
+ int status4 = 0, status6 = 0;
+ bool do_ipv4 = false, do_ipv6 = false;
+
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
+ do_ipv4 = true;
+ else if (type == CONNMAN_IPCONFIG_TYPE_IPV6)
+ do_ipv6 = true;
+ else
+ do_ipv4 = do_ipv6 = true;
+
+ DBG("type %d gateway ipv4 %p ipv6 %p", type, data->ipv4_gateway,
+ data->ipv6_gateway);
- DBG("element %p", element);
+ if (do_ipv4 && data->ipv4_gateway &&
+ data->ipv4_gateway->vpn) {
+ connman_inet_set_gateway_interface(data->index);
+ data->ipv4_gateway->active = true;
- sk = socket(PF_INET, SOCK_DGRAM, 0);
- if (sk < 0)
- return -1;
+ DBG("set %p index %d vpn %s index %d phy %s",
+ data, data->index, data->ipv4_gateway->vpn_ip,
+ data->ipv4_gateway->vpn_phy_index,
+ data->ipv4_gateway->vpn_phy_ip);
- memset(&ifr, 0, sizeof(ifr));
- ifr.ifr_ifindex = element->index;
+ __connman_service_indicate_default(data->service);
- if (ioctl(sk, SIOCGIFNAME, &ifr) < 0) {
- close(sk);
- return -1;
+ return;
}
- DBG("ifname %s", ifr.ifr_name);
+ if (do_ipv6 && data->ipv6_gateway &&
+ data->ipv6_gateway->vpn) {
+ connman_inet_set_ipv6_gateway_interface(data->index);
+ data->ipv6_gateway->active = true;
+
+ DBG("set %p index %d vpn %s index %d phy %s",
+ data, data->index, data->ipv6_gateway->vpn_ip,
+ data->ipv6_gateway->vpn_phy_index,
+ data->ipv6_gateway->vpn_phy_ip);
+
+ __connman_service_indicate_default(data->service);
- memset(&rt, 0, sizeof(rt));
- rt.rt_flags = RTF_UP | RTF_GATEWAY;
+ return;
+ }
- addr = (struct sockaddr_in *) &rt.rt_dst;
- addr->sin_family = AF_INET;
- addr->sin_addr.s_addr = INADDR_ANY;
+ index = __connman_service_get_index(data->service);
- addr = (struct sockaddr_in *) &rt.rt_gateway;
- addr->sin_family = AF_INET;
- addr->sin_addr.s_addr = inet_addr(gateway);
+ if (do_ipv4 && data->ipv4_gateway &&
+ g_strcmp0(data->ipv4_gateway->gateway,
+ "0.0.0.0") == 0) {
+ if (connman_inet_set_gateway_interface(index) < 0)
+ return;
+ data->ipv4_gateway->active = true;
+ goto done;
+ }
- addr = (struct sockaddr_in *) &rt.rt_genmask;
- addr->sin_family = AF_INET;
- addr->sin_addr.s_addr = INADDR_ANY;
+ if (do_ipv6 && data->ipv6_gateway &&
+ g_strcmp0(data->ipv6_gateway->gateway,
+ "::") == 0) {
+ if (connman_inet_set_ipv6_gateway_interface(index) < 0)
+ return;
+ data->ipv6_gateway->active = true;
+ goto done;
+ }
- err = ioctl(sk, SIOCADDRT, &rt);
- if (err < 0)
- DBG("default route setting failed (%s)", strerror(errno));
+ if (do_ipv6 && data->ipv6_gateway)
+ status6 = __connman_inet_add_default_to_table(RT_TABLE_MAIN,
+ index, data->ipv6_gateway->gateway);
- close(sk);
+ if (do_ipv4 && data->ipv4_gateway)
+ status4 = __connman_inet_add_default_to_table(RT_TABLE_MAIN,
+ index, data->ipv4_gateway->gateway);
- return err;
+ if (status4 < 0 || status6 < 0)
+ return;
+
+done:
+ __connman_service_indicate_default(data->service);
}
-static int del_route(struct connman_element *element, const char *gateway)
+static void unset_default_gateway(struct gateway_data *data,
+ enum connman_ipconfig_type type)
{
- struct ifreq ifr;
- struct rtentry rt;
- struct sockaddr_in *addr;
- int sk, err;
+ int index;
+ bool do_ipv4 = false, do_ipv6 = false;
- DBG("element %p", element);
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
+ do_ipv4 = true;
+ else if (type == CONNMAN_IPCONFIG_TYPE_IPV6)
+ do_ipv6 = true;
+ else
+ do_ipv4 = do_ipv6 = true;
- sk = socket(PF_INET, SOCK_DGRAM, 0);
- if (sk < 0)
- return -1;
+ DBG("type %d gateway ipv4 %p ipv6 %p", type, data->ipv4_gateway,
+ data->ipv6_gateway);
- memset(&ifr, 0, sizeof(ifr));
- ifr.ifr_ifindex = element->index;
+ if (do_ipv4 && data->ipv4_gateway &&
+ data->ipv4_gateway->vpn) {
+ connman_inet_clear_gateway_interface(data->index);
+ data->ipv4_gateway->active = false;
- if (ioctl(sk, SIOCGIFNAME, &ifr) < 0) {
- close(sk);
- return -1;
+ DBG("unset %p index %d vpn %s index %d phy %s",
+ data, data->index, data->ipv4_gateway->vpn_ip,
+ data->ipv4_gateway->vpn_phy_index,
+ data->ipv4_gateway->vpn_phy_ip);
+
+ return;
}
- DBG("ifname %s", ifr.ifr_name);
+ if (do_ipv6 && data->ipv6_gateway &&
+ data->ipv6_gateway->vpn) {
+ connman_inet_clear_ipv6_gateway_interface(data->index);
+ data->ipv6_gateway->active = false;
- memset(&rt, 0, sizeof(rt));
- rt.rt_flags = RTF_UP | RTF_GATEWAY;
+ DBG("unset %p index %d vpn %s index %d phy %s",
+ data, data->index, data->ipv6_gateway->vpn_ip,
+ data->ipv6_gateway->vpn_phy_index,
+ data->ipv6_gateway->vpn_phy_ip);
- addr = (struct sockaddr_in *) &rt.rt_dst;
- addr->sin_family = AF_INET;
- addr->sin_addr.s_addr = INADDR_ANY;
+ return;
+ }
- addr = (struct sockaddr_in *) &rt.rt_gateway;
- addr->sin_family = AF_INET;
- addr->sin_addr.s_addr = inet_addr(gateway);
+ index = __connman_service_get_index(data->service);
- addr = (struct sockaddr_in *) &rt.rt_genmask;
- addr->sin_family = AF_INET;
- addr->sin_addr.s_addr = INADDR_ANY;
+ if (do_ipv4 && data->ipv4_gateway &&
+ g_strcmp0(data->ipv4_gateway->gateway,
+ "0.0.0.0") == 0) {
+ connman_inet_clear_gateway_interface(index);
+ data->ipv4_gateway->active = false;
+ return;
+ }
- err = ioctl(sk, SIOCDELRT, &rt);
- if (err < 0)
- DBG("default route removal failed (%s)", strerror(errno));
+ if (do_ipv6 && data->ipv6_gateway &&
+ g_strcmp0(data->ipv6_gateway->gateway,
+ "::") == 0) {
+ connman_inet_clear_ipv6_gateway_interface(index);
+ data->ipv6_gateway->active = false;
+ return;
+ }
- close(sk);
+ if (do_ipv6 && data->ipv6_gateway)
+ connman_inet_clear_ipv6_gateway_address(index,
+ data->ipv6_gateway->gateway);
- return err;
+ if (do_ipv4 && data->ipv4_gateway)
+ connman_inet_clear_gateway_address(index,
+ data->ipv4_gateway->gateway);
}
-static DBusMessage *get_properties(DBusConnection *conn,
- DBusMessage *msg, void *data)
+static struct gateway_data *find_default_gateway(void)
{
- struct connman_element *element = data;
- DBusMessage *reply;
- DBusMessageIter array, dict;
- connman_uint8_t strength = 0;
- const char *type = NULL, *method = NULL;
- const char *address = NULL, *netmask = NULL, *gateway = NULL;
+ struct connman_service *service;
+
+ service = connman_service_get_default();
+ if (!service)
+ return NULL;
- DBG("conn %p", conn);
+ return g_hash_table_lookup(gateway_hash, service);
+}
- if (__connman_security_check_privilege(msg,
- CONNMAN_SECURITY_PRIVILEGE_PUBLIC) < 0)
- return __connman_error_permission_denied(msg);
+static bool choose_default_gateway(struct gateway_data *data,
+ struct gateway_data *candidate)
+{
+ bool downgraded = false;
+
+ /*
+ * If the current default is not active, then we mark
+ * this one as default. If the other one is already active
+ * we mark this one as non default.
+ */
+ if (data->ipv4_gateway && candidate->ipv4_gateway) {
+
+ if (!candidate->ipv4_gateway->active) {
+ DBG("ipv4 downgrading %p", candidate);
+ unset_default_gateway(candidate,
+ CONNMAN_IPCONFIG_TYPE_IPV4);
+ }
+
+ if (candidate->ipv4_gateway->active &&
+ __connman_service_compare(candidate->service,
+ data->service) < 0) {
+ DBG("ipv4 downgrading this %p", data);
+ unset_default_gateway(data, CONNMAN_IPCONFIG_TYPE_IPV4);
+ downgraded = true;
+ }
+ }
- reply = dbus_message_new_method_return(msg);
- if (reply == NULL)
- return NULL;
+ if (data->ipv6_gateway && candidate->ipv6_gateway) {
+ if (!candidate->ipv6_gateway->active) {
+ DBG("ipv6 downgrading %p", candidate);
+ unset_default_gateway(candidate,
+ CONNMAN_IPCONFIG_TYPE_IPV6);
+ }
+
+ if (candidate->ipv6_gateway->active &&
+ __connman_service_compare(candidate->service,
+ data->service) < 0) {
+ DBG("ipv6 downgrading this %p", data);
+ unset_default_gateway(data, CONNMAN_IPCONFIG_TYPE_IPV6);
+ downgraded = true;
+ }
+ }
- dbus_message_iter_init_append(reply, &array);
+ return downgraded;
+}
- dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY,
- DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
- DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
- DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+static void connection_newgateway(int index, const char *gateway)
+{
+ struct gateway_config *config;
+ struct gateway_data *data;
+ GHashTableIter iter;
+ gpointer value, key;
+ bool found = false;
- connman_element_get_static_property(element, "Type", &type);
+ DBG("index %d gateway %s", index, gateway);
- if (type != NULL)
- connman_dbus_dict_append_variant(&dict, "Type",
- DBUS_TYPE_STRING, &type);
+ config = find_gateway(index, gateway);
+ if (!config)
+ return;
- connman_element_get_static_property(element, "Strength", &strength);
- if (strength > 0)
- connman_dbus_dict_append_variant(&dict, "Strength",
- DBUS_TYPE_BYTE, &strength);
+ config->active = true;
- if (element->devname != NULL)
- connman_dbus_dict_append_variant(&dict, "Interface",
- DBUS_TYPE_STRING, &element->devname);
+ /*
+ * It is possible that we have two default routes atm
+ * if there are two gateways waiting rtnl activation at the
+ * same time.
+ */
+ data = lookup_gateway_data(config);
+ if (!data)
+ return;
- connman_dbus_dict_append_variant(&dict, "Default",
- DBUS_TYPE_BOOLEAN, &element->enabled);
+ if (data->default_checked)
+ return;
- connman_element_get_value(element,
- CONNMAN_PROPERTY_ID_IPV4_METHOD, &method);
+ /*
+ * The next checks are only done once, otherwise setting
+ * the default gateway could lead into rtnl forever loop.
+ */
- connman_element_get_value(element,
- CONNMAN_PROPERTY_ID_IPV4_ADDRESS, &address);
- connman_element_get_value(element,
- CONNMAN_PROPERTY_ID_IPV4_NETMASK, &netmask);
- connman_element_get_value(element,
- CONNMAN_PROPERTY_ID_IPV4_GATEWAY, &gateway);
+ g_hash_table_iter_init(&iter, gateway_hash);
- if (method != NULL)
- connman_dbus_dict_append_variant(&dict, "IPv4.Method",
- DBUS_TYPE_STRING, &method);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ struct gateway_data *candidate = value;
- if (address != NULL)
- connman_dbus_dict_append_variant(&dict, "IPv4.Address",
- DBUS_TYPE_STRING, &address);
+ if (candidate == data)
+ continue;
- if (netmask != NULL)
- connman_dbus_dict_append_variant(&dict, "IPv4.Netmask",
- DBUS_TYPE_STRING, &netmask);
+ found = choose_default_gateway(data, candidate);
+ if (found)
+ break;
+ }
- if (gateway != NULL)
- connman_dbus_dict_append_variant(&dict, "IPv4.Gateway",
- DBUS_TYPE_STRING, &gateway);
+ if (!found) {
+ if (data->ipv4_gateway)
+ set_default_gateway(data, CONNMAN_IPCONFIG_TYPE_IPV4);
- dbus_message_iter_close_container(&array, &dict);
+ if (data->ipv6_gateway)
+ set_default_gateway(data, CONNMAN_IPCONFIG_TYPE_IPV6);
+ }
- return reply;
+ data->default_checked = true;
}
-static DBusMessage *set_property(DBusConnection *conn,
- DBusMessage *msg, void *data)
+static void remove_gateway(gpointer user_data)
{
- DBusMessageIter iter, value;
- const char *name;
+ struct gateway_data *data = user_data;
- DBG("conn %p", conn);
+ DBG("gateway ipv4 %p ipv6 %p", data->ipv4_gateway, data->ipv6_gateway);
- if (dbus_message_iter_init(msg, &iter) == FALSE)
- return __connman_error_invalid_arguments(msg);
+ if (data->ipv4_gateway) {
+ g_free(data->ipv4_gateway->gateway);
+ g_free(data->ipv4_gateway->vpn_ip);
+ g_free(data->ipv4_gateway->vpn_phy_ip);
+ g_free(data->ipv4_gateway);
+ }
- dbus_message_iter_get_basic(&iter, &name);
- dbus_message_iter_next(&iter);
- dbus_message_iter_recurse(&iter, &value);
+ if (data->ipv6_gateway) {
+ g_free(data->ipv6_gateway->gateway);
+ g_free(data->ipv6_gateway->vpn_ip);
+ g_free(data->ipv6_gateway->vpn_phy_ip);
+ g_free(data->ipv6_gateway);
+ }
- if (__connman_security_check_privilege(msg,
- CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
- return __connman_error_permission_denied(msg);
+ connman_service_unref(data->service);
- return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+ g_free(data);
}
-static GDBusMethodTable connection_methods[] = {
- { "GetProperties", "", "a{sv}", get_properties },
- { "SetProperty", "sv", "", set_property },
- { },
-};
+static void connection_delgateway(int index, const char *gateway)
+{
+ struct gateway_config *config;
+ struct gateway_data *data;
+
+ DBG("index %d gateway %s", index, gateway);
+
+ config = find_gateway(index, gateway);
+ if (config)
+ config->active = false;
+
+ data = find_default_gateway();
+ if (data)
+ set_default_gateway(data, CONNMAN_IPCONFIG_TYPE_ALL);
+}
-static GDBusSignalTable connection_signals[] = {
- { "PropertyChanged", "sv" },
- { },
+static struct connman_rtnl connection_rtnl = {
+ .name = "connection",
+ .newgateway = connection_newgateway,
+ .delgateway = connection_delgateway,
};
-static DBusConnection *connection;
+static struct gateway_data *find_active_gateway(void)
+{
+ GHashTableIter iter;
+ gpointer value, key;
+
+ DBG("");
+
+ g_hash_table_iter_init(&iter, gateway_hash);
+
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ struct gateway_data *data = value;
+
+ if (data->ipv4_gateway &&
+ data->ipv4_gateway->active)
+ return data;
+
+ if (data->ipv6_gateway &&
+ data->ipv6_gateway->active)
+ return data;
+ }
+
+ return NULL;
+}
-static void emit_connections_signal(void)
+static void add_host_route(int family, int index, const char *gateway,
+ enum connman_service_type service_type)
{
+ switch (family) {
+ case AF_INET:
+ if (g_strcmp0(gateway, "0.0.0.0") != 0) {
+ /*
+ * We must not set route to the phy dev gateway in
+ * VPN link. The packets to VPN link might be routed
+ * back to itself and not routed into phy link gateway.
+ */
+ if (service_type != CONNMAN_SERVICE_TYPE_VPN)
+ connman_inet_add_host_route(index, gateway,
+ NULL);
+ } else {
+ /*
+ * Add host route to P-t-P link so that services can
+ * be moved around and we can have some link to P-t-P
+ * network (although those P-t-P links have limited
+ * usage if default route is not directed to them)
+ */
+ char *dest;
+ if (connman_inet_get_dest_addr(index, &dest) == 0) {
+ connman_inet_add_host_route(index, dest, NULL);
+ g_free(dest);
+ }
+ }
+ break;
+
+ case AF_INET6:
+ if (g_strcmp0(gateway, "::") != 0) {
+ if (service_type != CONNMAN_SERVICE_TYPE_VPN)
+ connman_inet_add_ipv6_host_route(index,
+ gateway, NULL);
+ } else {
+ /* P-t-P link, add route to destination */
+ char *dest;
+ if (connman_inet_ipv6_get_dest_addr(index,
+ &dest) == 0) {
+ connman_inet_add_ipv6_host_route(index, dest,
+ NULL);
+ g_free(dest);
+ }
+ }
+ break;
+ }
}
-static int register_interface(struct connman_element *element)
+int __connman_connection_gateway_add(struct connman_service *service,
+ const char *gateway,
+ enum connman_ipconfig_type type,
+ const char *peer)
{
- DBG("element %p name %s", element, element->name);
+ struct gateway_data *active_gateway = NULL;
+ struct gateway_data *new_gateway = NULL;
+ enum connman_ipconfig_type type4 = CONNMAN_IPCONFIG_TYPE_UNKNOWN,
+ type6 = CONNMAN_IPCONFIG_TYPE_UNKNOWN;
+ enum connman_service_type service_type =
+ connman_service_get_type(service);
+ int index;
+
+ index = __connman_service_get_index(service);
+
+ /*
+ * If gateway is NULL, it's a point to point link and the default
+ * gateway for ipv4 is 0.0.0.0 and for ipv6 is ::, meaning the
+ * interface
+ */
+ if (!gateway && type == CONNMAN_IPCONFIG_TYPE_IPV4)
+ gateway = "0.0.0.0";
+
+ if (!gateway && type == CONNMAN_IPCONFIG_TYPE_IPV6)
+ gateway = "::";
+
+ DBG("service %p index %d gateway %s vpn ip %s type %d",
+ service, index, gateway, peer, type);
+
+ new_gateway = add_gateway(service, index, gateway, type);
+ if (!new_gateway)
+ return -EINVAL;
+
+ active_gateway = find_active_gateway();
+
+ DBG("active %p index %d new %p", active_gateway,
+ active_gateway ? active_gateway->index : -1, new_gateway);
+
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV4 &&
+ new_gateway->ipv4_gateway) {
+ add_host_route(AF_INET, index, gateway, service_type);
+ __connman_service_nameserver_add_routes(service,
+ new_gateway->ipv4_gateway->gateway);
+ type4 = CONNMAN_IPCONFIG_TYPE_IPV4;
+ }
+
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV6 &&
+ new_gateway->ipv6_gateway) {
+ add_host_route(AF_INET6, index, gateway, service_type);
+ __connman_service_nameserver_add_routes(service,
+ new_gateway->ipv6_gateway->gateway);
+ type6 = CONNMAN_IPCONFIG_TYPE_IPV6;
+ }
+
+ if (service_type == CONNMAN_SERVICE_TYPE_VPN) {
+
+ set_vpn_routes(new_gateway, service, gateway, type, peer,
+ active_gateway);
+
+ } else {
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV4 &&
+ new_gateway->ipv4_gateway)
+ new_gateway->ipv4_gateway->vpn = false;
+
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV6 &&
+ new_gateway->ipv6_gateway)
+ new_gateway->ipv6_gateway->vpn = false;
+ }
- if (g_dbus_register_interface(connection, element->path,
- CONNMAN_CONNECTION_INTERFACE,
- connection_methods, connection_signals,
- NULL, element, NULL) == FALSE) {
- connman_error("Failed to register %s connection", element->path);
- return -EIO;
+ if (!active_gateway) {
+ set_default_gateway(new_gateway, type);
+ goto done;
}
- emit_connections_signal();
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV4 &&
+ new_gateway->ipv4_gateway &&
+ new_gateway->ipv4_gateway->vpn) {
+ if (!__connman_service_is_split_routing(new_gateway->service))
+ connman_inet_clear_gateway_address(
+ active_gateway->index,
+ active_gateway->ipv4_gateway->gateway);
+ }
+
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV6 &&
+ new_gateway->ipv6_gateway &&
+ new_gateway->ipv6_gateway->vpn) {
+ if (!__connman_service_is_split_routing(new_gateway->service))
+ connman_inet_clear_ipv6_gateway_address(
+ active_gateway->index,
+ active_gateway->ipv6_gateway->gateway);
+ }
+
+done:
+ if (type4 == CONNMAN_IPCONFIG_TYPE_IPV4)
+ __connman_service_ipconfig_indicate_state(service,
+ CONNMAN_SERVICE_STATE_READY,
+ CONNMAN_IPCONFIG_TYPE_IPV4);
+ if (type6 == CONNMAN_IPCONFIG_TYPE_IPV6)
+ __connman_service_ipconfig_indicate_state(service,
+ CONNMAN_SERVICE_STATE_READY,
+ CONNMAN_IPCONFIG_TYPE_IPV6);
return 0;
}
-static void unregister_interface(struct connman_element *element)
+void __connman_connection_gateway_remove(struct connman_service *service,
+ enum connman_ipconfig_type type)
{
- DBG("element %p name %s", element, element->name);
+ struct gateway_data *data = NULL;
+ bool set_default4 = false, set_default6 = false;
+ bool do_ipv4 = false, do_ipv6 = false;
+ int err;
+
+ DBG("service %p type %d", service, type);
+
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
+ do_ipv4 = true;
+ else if (type == CONNMAN_IPCONFIG_TYPE_IPV6)
+ do_ipv6 = true;
+ else
+ do_ipv4 = do_ipv6 = true;
+
+ __connman_service_nameserver_del_routes(service, type);
- emit_connections_signal();
+ data = g_hash_table_lookup(gateway_hash, service);
+ if (!data)
+ return;
- g_dbus_unregister_interface(connection, element->path,
- CONNMAN_CONNECTION_INTERFACE);
+ if (do_ipv4 && data->ipv4_gateway)
+ set_default4 = data->ipv4_gateway->vpn;
+
+ if (do_ipv6 && data->ipv6_gateway)
+ set_default6 = data->ipv6_gateway->vpn;
+
+ DBG("ipv4 gateway %s ipv6 gateway %s vpn %d/%d",
+ data->ipv4_gateway ? data->ipv4_gateway->gateway : "<null>",
+ data->ipv6_gateway ? data->ipv6_gateway->gateway : "<null>",
+ set_default4, set_default6);
+
+ if (do_ipv4 && data->ipv4_gateway &&
+ data->ipv4_gateway->vpn && data->index >= 0)
+ connman_inet_del_host_route(data->ipv4_gateway->vpn_phy_index,
+ data->ipv4_gateway->gateway);
+
+ if (do_ipv6 && data->ipv6_gateway &&
+ data->ipv6_gateway->vpn && data->index >= 0)
+ connman_inet_del_ipv6_host_route(
+ data->ipv6_gateway->vpn_phy_index,
+ data->ipv6_gateway->gateway);
+
+ err = disable_gateway(data, type);
+
+ /*
+ * We remove the service from the hash only if all the gateway
+ * settings are to be removed.
+ */
+ if (do_ipv4 == do_ipv6 ||
+ (data->ipv4_gateway && !data->ipv6_gateway
+ && do_ipv4) ||
+ (data->ipv6_gateway && !data->ipv4_gateway
+ && do_ipv6)) {
+ g_hash_table_remove(gateway_hash, service);
+ } else
+ DBG("Not yet removing gw ipv4 %p/%d ipv6 %p/%d",
+ data->ipv4_gateway, do_ipv4,
+ data->ipv6_gateway, do_ipv6);
+
+ /* with vpn this will be called after the network was deleted,
+ * we need to call set_default here because we will not receive any
+ * gateway delete notification.
+ * We hit the same issue if remove_gateway() fails.
+ */
+ if (set_default4 || set_default6 || err < 0) {
+ data = find_default_gateway();
+ if (data)
+ set_default_gateway(data, type);
+ }
}
-static int connection_probe(struct connman_element *element)
+bool __connman_connection_update_gateway(void)
{
- const char *gateway = NULL;
+ struct gateway_data *default_gateway;
+ bool updated = false;
+ GHashTableIter iter;
+ gpointer value, key;
- DBG("element %p name %s", element, element->name);
+ if (!gateway_hash)
+ return updated;
- if (element->parent == NULL)
- return -ENODEV;
+ default_gateway = find_default_gateway();
- if (element->parent->type != CONNMAN_ELEMENT_TYPE_IPV4)
- return -ENODEV;
+ DBG("default %p", default_gateway);
- connman_element_get_value(element,
- CONNMAN_PROPERTY_ID_IPV4_GATEWAY, &gateway);
+ /*
+ * There can be multiple active gateways so we need to
+ * check them all.
+ */
+ g_hash_table_iter_init(&iter, gateway_hash);
- DBG("gateway %s", gateway);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ struct gateway_data *active_gateway = value;
- if (register_interface(element) < 0)
- return -ENODEV;
+ if (active_gateway == default_gateway)
+ continue;
- if (gateway == NULL)
- return 0;
+ if (active_gateway->ipv4_gateway &&
+ active_gateway->ipv4_gateway->active) {
- if (g_slist_length(gateway_list) > 0) {
- DBG("default gateway already present");
- return 0;
- }
+ unset_default_gateway(active_gateway,
+ CONNMAN_IPCONFIG_TYPE_IPV4);
+ updated = true;
+ }
- set_route(element, gateway);
+ if (active_gateway->ipv6_gateway &&
+ active_gateway->ipv6_gateway->active) {
- connman_element_set_enabled(element, TRUE);
+ unset_default_gateway(active_gateway,
+ CONNMAN_IPCONFIG_TYPE_IPV6);
+ updated = true;
+ }
+ }
- return 0;
+ /*
+ * Set default gateway if it has been updated or if it has not been
+ * set as active yet.
+ */
+ if (default_gateway) {
+ if (default_gateway->ipv4_gateway &&
+ (updated || !default_gateway->ipv4_gateway->active))
+ set_default_gateway(default_gateway,
+ CONNMAN_IPCONFIG_TYPE_IPV4);
+
+ if (default_gateway->ipv6_gateway &&
+ (updated || !default_gateway->ipv6_gateway->active))
+ set_default_gateway(default_gateway,
+ CONNMAN_IPCONFIG_TYPE_IPV6);
+ }
+
+ return updated;
}
-static void connection_remove(struct connman_element *element)
+int __connman_connection_get_vpn_index(int phy_index)
{
- const char *gateway = NULL;
-
- DBG("element %p name %s", element, element->name);
+ GHashTableIter iter;
+ gpointer value, key;
- unregister_interface(element);
+ g_hash_table_iter_init(&iter, gateway_hash);
- connman_element_get_value(element,
- CONNMAN_PROPERTY_ID_IPV4_GATEWAY, &gateway);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ struct gateway_data *data = value;
- DBG("gateway %s", gateway);
+ if (data->ipv4_gateway &&
+ data->ipv4_gateway->vpn_phy_index == phy_index)
+ return data->index;
- if (gateway == NULL)
- return;
+ if (data->ipv6_gateway &&
+ data->ipv6_gateway->vpn_phy_index == phy_index)
+ return data->index;
+ }
- del_route(element, gateway);
+ return -1;
}
-static struct connman_driver connection_driver = {
- .name = "connection",
- .type = CONNMAN_ELEMENT_TYPE_CONNECTION,
- .priority = CONNMAN_DRIVER_PRIORITY_LOW,
- .probe = connection_probe,
- .remove = connection_remove,
-};
-
int __connman_connection_init(void)
{
+ int err;
+
DBG("");
- connection = connman_dbus_get_connection();
+ gateway_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+ NULL, remove_gateway);
- if (connman_rtnl_register(&connection_rtnl) < 0)
+ err = connman_rtnl_register(&connection_rtnl);
+ if (err < 0)
connman_error("Failed to setup RTNL gateway driver");
- connman_rtnl_send_getroute();
-
- return connman_driver_register(&connection_driver);
+ return err;
}
void __connman_connection_cleanup(void)
{
- GSList *list;
+ GHashTableIter iter;
+ gpointer value, key;
DBG("");
- connman_driver_unregister(&connection_driver);
-
connman_rtnl_unregister(&connection_rtnl);
- for (list = gateway_list; list; list = list->next) {
- struct gateway_data *data = list->data;
+ g_hash_table_iter_init(&iter, gateway_hash);
- DBG("index %d gateway %s", data->index, data->gateway);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ struct gateway_data *data = value;
- g_free(data->gateway);
- g_free(data);
- list->data = NULL;
+ disable_gateway(data, CONNMAN_IPCONFIG_TYPE_ALL);
}
- g_slist_free(gateway_list);
- gateway_list = NULL;
-
- dbus_connection_unref(connection);
+ g_hash_table_destroy(gateway_hash);
+ gateway_hash = NULL;
}