+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) == TRUE) {
+ struct gateway_data *data = value;
+
+ if (data->ipv4_gateway != NULL &&
+ data->ipv4_gateway->active == TRUE)
+ return data;
+
+ if (data->ipv6_gateway != NULL &&
+ data->ipv6_gateway->active == TRUE)
+ return data;
+ }
+
+ return NULL;
+}
+
+static void update_order(void)
+{
+ GHashTableIter iter;
+ gpointer value, key;
+
+ DBG("");
+
+ g_hash_table_iter_init(&iter, gateway_hash);
+
+ while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
+ struct gateway_data *data = value;
+
+ data->order = __connman_service_get_order(data->service);
+ }
+}
+
+void __connman_connection_gateway_activate(struct connman_service *service,
+ enum connman_ipconfig_type type)
+{
+ struct gateway_data *data = NULL;
+
+ data = g_hash_table_lookup(gateway_hash, service);
+ if (data == NULL)
+ return;
+
+ DBG("gateway %p/%p type %d", data->ipv4_gateway,
+ data->ipv6_gateway, type);
+
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
+ data->ipv4_gateway->active = TRUE;
+ else if (type == CONNMAN_IPCONFIG_TYPE_IPV6)
+ data->ipv6_gateway->active = TRUE;
+}
+
+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;
+ }
+}
+
+int __connman_connection_gateway_add(struct connman_service *service,
+ const char *gateway,
+ enum connman_ipconfig_type type,
+ const char *peer)
+{
+ 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 == NULL && type == CONNMAN_IPCONFIG_TYPE_IPV4)
+ gateway = "0.0.0.0";
+
+ if (gateway == NULL && 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 == NULL)
+ 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 != NULL) {
+ 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 != NULL) {
+ 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 != NULL)
+ new_gateway->ipv4_gateway->vpn = FALSE;
+
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV6 &&
+ new_gateway->ipv6_gateway != NULL)
+ new_gateway->ipv6_gateway->vpn = FALSE;
+ }
+
+ if (active_gateway == NULL) {
+ set_default_gateway(new_gateway, type);
+ goto done;
+ }
+
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV4 &&
+ new_gateway->ipv4_gateway != NULL &&
+ new_gateway->ipv4_gateway->vpn == TRUE) {
+ if (__connman_service_is_split_routing(new_gateway->service) ==
+ FALSE)
+ connman_inet_clear_gateway_address(
+ active_gateway->index,
+ active_gateway->ipv4_gateway->gateway);
+ }
+
+ if (type == CONNMAN_IPCONFIG_TYPE_IPV6 &&
+ new_gateway->ipv6_gateway != NULL &&
+ new_gateway->ipv6_gateway->vpn == TRUE) {
+ if (__connman_service_is_split_routing(new_gateway->service) ==
+ FALSE)
+ 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;
+}
+
+void __connman_connection_gateway_remove(struct connman_service *service,
+ enum connman_ipconfig_type type)
+{
+ struct gateway_data *data = NULL;
+ gboolean set_default4 = FALSE, set_default6 = FALSE;
+ int 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);
+
+ data = g_hash_table_lookup(gateway_hash, service);
+ if (data == NULL)
+ return;
+
+ if (do_ipv4 == TRUE && data->ipv4_gateway != NULL)
+ set_default4 = data->ipv4_gateway->vpn;
+
+ if (do_ipv6 == TRUE && data->ipv6_gateway != NULL)
+ 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 == TRUE && data->ipv4_gateway != NULL &&
+ data->ipv4_gateway->vpn == TRUE && data->index >= 0)
+ connman_inet_del_host_route(data->index,
+ data->ipv4_gateway->gateway);
+
+ if (do_ipv6 == TRUE && data->ipv6_gateway != NULL &&
+ data->ipv6_gateway->vpn == TRUE && data->index >= 0)
+ connman_inet_del_ipv6_host_route(data->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 != NULL && data->ipv6_gateway == NULL
+ && do_ipv4 == TRUE) ||
+ (data->ipv6_gateway != NULL && data->ipv4_gateway == NULL
+ && do_ipv6 == TRUE)
+ ) {
+ connman_service_unref(service);
+ 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 recieve 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 != NULL)
+ set_default_gateway(data, type);
+ }
+}
+
+gboolean __connman_connection_update_gateway(void)
+{
+ struct gateway_data *default_gateway;
+ gboolean updated = FALSE;
+ GHashTableIter iter;
+ gpointer value, key;
+
+ if (gateway_hash == NULL)
+ return updated;
+
+ update_order();
+
+ default_gateway = find_default_gateway();
+
+ __connman_service_update_ordering();
+
+ DBG("default %p", default_gateway);
+
+ /*
+ * There can be multiple active gateways so we need to
+ * check them all.
+ */
+ g_hash_table_iter_init(&iter, gateway_hash);
+
+ while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
+ struct gateway_data *active_gateway = value;
+
+ if (active_gateway == default_gateway)
+ continue;
+
+ if (active_gateway->ipv4_gateway != NULL &&
+ active_gateway->ipv4_gateway->active == TRUE) {
+
+ unset_default_gateway(active_gateway,
+ CONNMAN_IPCONFIG_TYPE_IPV4);
+ updated = TRUE;
+ }
+
+ if (active_gateway->ipv6_gateway != NULL &&
+ active_gateway->ipv6_gateway->active == TRUE) {
+
+ unset_default_gateway(active_gateway,
+ CONNMAN_IPCONFIG_TYPE_IPV6);
+ updated = TRUE;
+ }
+ }
+
+ if (updated && default_gateway != NULL) {
+ if (default_gateway->ipv4_gateway)
+ set_default_gateway(default_gateway,
+ CONNMAN_IPCONFIG_TYPE_IPV4);
+
+ if (default_gateway->ipv6_gateway)
+ set_default_gateway(default_gateway,
+ CONNMAN_IPCONFIG_TYPE_IPV6);
+ }
+
+ return updated;
+}
+
+int __connman_connection_get_vpn_index(int phy_index)
+{
+ GHashTableIter iter;
+ gpointer value, key;
+
+ g_hash_table_iter_init(&iter, gateway_hash);
+
+ while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
+ struct gateway_data *data = value;
+
+ if (data->ipv4_gateway != NULL &&
+ data->ipv4_gateway->vpn_phy_index == phy_index)
+ return data->index;
+
+ if (data->ipv6_gateway != NULL &&
+ data->ipv6_gateway->vpn_phy_index == phy_index)
+ return data->index;
+ }
+
+ return -1;
+}
+