Imported Upstream version 1.38
[platform/upstream/connman.git] / src / network.c
index ed56210..f2ab16b 100644 (file)
@@ -27,6 +27,8 @@
 #include <string.h>
 
 #include "connman.h"
+#include <connman/acd.h>
+#include "src/shared/arp.h"
 
 /*
  * How many times to send RS with the purpose of
  */
 #define RS_REFRESH_TIMEOUT     3
 
+/*
+ * As per RFC 4861, a host should transmit up to MAX_RTR_SOLICITATIONS(3)
+ * Router Solicitation messages, each separated by at least
+ * RTR_SOLICITATION_INTERVAL(4) seconds to obtain RA for IPv6 auto-configuration.
+ */
+#define RTR_SOLICITATION_INTERVAL      4
+
+#define DHCP_RETRY_TIMEOUT     10
+
 static GSList *network_list = NULL;
 static GSList *driver_list = NULL;
 
@@ -60,6 +71,9 @@ struct connman_network {
        int index;
        int router_solicit_count;
        int router_solicit_refresh_count;
+       struct acd_host *acd_host;
+       guint ipv4ll_timeout;
+       guint dhcp_timeout;
 
        struct connman_network_driver *driver;
        void *driver_data;
@@ -90,6 +104,7 @@ struct connman_network {
                char *private_key_passphrase;
                char *phase2_auth;
                bool wps;
+               bool wps_advertizing;
                bool use_wps;
                char *pin_wps;
        } wifi;
@@ -146,6 +161,260 @@ static void set_configuration(struct connman_network *network,
                                        type);
 }
 
+void connman_network_append_acddbus(DBusMessageIter *dict,
+               struct connman_network *network)
+{
+       if (!network->acd_host)
+               return;
+
+       acd_host_append_dbus_property(network->acd_host, dict);
+}
+
+static int start_acd(struct connman_network *network);
+
+static void remove_ipv4ll_timeout(struct connman_network *network)
+{
+       if (network->ipv4ll_timeout > 0) {
+               g_source_remove(network->ipv4ll_timeout);
+               network->ipv4ll_timeout = 0;
+       }
+}
+
+static void acd_host_ipv4_available(struct acd_host *acd, gpointer user_data)
+{
+       struct connman_network *network = user_data;
+       struct connman_service *service;
+       struct connman_ipconfig *ipconfig_ipv4;
+       int err;
+
+       if (!network)
+               return;
+
+       service = connman_service_lookup_from_network(network);
+       if (!service)
+               return;
+
+       ipconfig_ipv4 = __connman_service_get_ip4config(service);
+       if (!ipconfig_ipv4) {
+               connman_error("Service has no IPv4 configuration");
+               return;
+       }
+
+       err = __connman_ipconfig_address_add(ipconfig_ipv4);
+       if (err < 0)
+               goto err;
+
+       err = __connman_ipconfig_gateway_add(ipconfig_ipv4);
+       if (err < 0)
+               goto err;
+
+       __connman_service_save(service);
+
+       return;
+
+err:
+       connman_network_set_error(__connman_service_get_network(service),
+                               CONNMAN_NETWORK_ERROR_CONFIGURE_FAIL);
+}
+
+static int start_ipv4ll(struct connman_network *network)
+{
+       struct connman_service *service;
+       struct connman_ipconfig *ipconfig_ipv4;
+       struct in_addr addr;
+       char *address;
+
+       service = connman_service_lookup_from_network(network);
+       if (!service)
+               return -EINVAL;
+
+       ipconfig_ipv4 = __connman_service_get_ip4config(service);
+       if (!ipconfig_ipv4) {
+               connman_error("Service has no IPv4 configuration");
+               return -EINVAL;
+       }
+
+       /* Apply random IPv4 address. */
+       addr.s_addr = htonl(arp_random_ip());
+       address = inet_ntoa(addr);
+       if (!address) {
+               connman_error("Could not convert IPv4LL random address %u",
+                               addr.s_addr);
+               return -EINVAL;
+       }
+       __connman_ipconfig_set_local(ipconfig_ipv4, address);
+
+       connman_info("Probing IPv4LL address %s", address);
+       return start_acd(network);
+}
+
+static gboolean start_ipv4ll_ontimeout(gpointer data)
+{
+       struct connman_network *network = data;
+
+       if (!network)
+               return FALSE;
+
+       /* Start IPv4LL ACD. */
+       start_ipv4ll(network);
+
+       return FALSE;
+}
+
+static void acd_host_ipv4_lost(struct acd_host *acd, gpointer user_data)
+{
+       struct connman_network *network = user_data;
+       struct connman_service *service;
+       struct connman_ipconfig *ipconfig_ipv4;
+       enum connman_ipconfig_type type;
+       enum connman_ipconfig_method method;
+
+       if (!network)
+               return;
+
+       service = connman_service_lookup_from_network(network);
+       if (!service)
+               return;
+
+       ipconfig_ipv4 = __connman_service_get_ip4config(service);
+       if (!ipconfig_ipv4) {
+               connman_error("Service has no IPv4 configuration");
+               return;
+       }
+
+       type = __connman_ipconfig_get_config_type(ipconfig_ipv4);
+       if (type != CONNMAN_IPCONFIG_TYPE_IPV4)
+               return;
+
+       __connman_ipconfig_address_remove(ipconfig_ipv4);
+
+       method = __connman_ipconfig_get_method(ipconfig_ipv4);
+       if (method == CONNMAN_IPCONFIG_METHOD_DHCP) {
+               /*
+                * We have one more chance for DHCP. If this fails
+                * acd_host_ipv4_conflict will be called.
+                */
+               network = __connman_service_get_network(service);
+               if (network)
+                       __connman_network_enable_ipconfig(network, ipconfig_ipv4);
+       } else {
+               /* Start IPv4LL ACD. */
+               start_ipv4ll(network);
+       }
+}
+
+static void acd_host_ipv4_conflict(struct acd_host *acd, gpointer user_data)
+{
+       struct connman_network *network = user_data;
+       struct connman_service *service;
+       struct connman_ipconfig *ipconfig_ipv4;
+       enum connman_ipconfig_method method;
+
+       service = connman_service_lookup_from_network(network);
+       if (!service)
+               return;
+
+       ipconfig_ipv4 = __connman_service_get_ip4config(service);
+       if (!ipconfig_ipv4) {
+               connman_error("Service has no IPv4 configuration");
+               return;
+       }
+
+       method = __connman_ipconfig_get_method(ipconfig_ipv4);
+       connman_info("%s conflict counts=%u", __FUNCTION__,
+                       acd_host_get_conflicts_count(acd));
+
+       if (method == CONNMAN_IPCONFIG_METHOD_DHCP &&
+                       acd_host_get_conflicts_count(acd) < 2) {
+               connman_info("%s Sending DHCP decline", __FUNCTION__);
+               __connman_dhcp_decline(ipconfig_ipv4);
+
+               connman_network_set_connected_dhcp_later(network, DHCP_RETRY_TIMEOUT);
+               __connman_ipconfig_set_local(ipconfig_ipv4, NULL);
+       } else {
+               if (method == CONNMAN_IPCONFIG_METHOD_DHCP) {
+                       __connman_ipconfig_set_method(ipconfig_ipv4,
+                                       CONNMAN_IPCONFIG_METHOD_AUTO);
+                       __connman_dhcp_decline(ipconfig_ipv4);
+               }
+               /* Start IPv4LL ACD. */
+               start_ipv4ll(network);
+       }
+}
+
+static void acd_host_ipv4_maxconflict(struct acd_host *acd, gpointer user_data)
+{
+       struct connman_network *network = user_data;
+
+       remove_ipv4ll_timeout(network);
+       connman_info("Had maximum number of conflicts. Next IPv4LL address will be "
+                       "tried in %d seconds", RATE_LIMIT_INTERVAL);
+       /* Wait, then start IPv4LL ACD. */
+       network->ipv4ll_timeout =
+               g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+                               RATE_LIMIT_INTERVAL,
+                               start_ipv4ll_ontimeout,
+                               network,
+                               NULL);
+}
+
+static int start_acd(struct connman_network *network)
+{
+       struct connman_service *service;
+       struct connman_ipconfig *ipconfig_ipv4;
+       const char* address;
+       struct in_addr addr;
+
+       remove_ipv4ll_timeout(network);
+
+       service = connman_service_lookup_from_network(network);
+       if (!service)
+               return -EINVAL;
+
+       ipconfig_ipv4 = __connman_service_get_ip4config(service);
+       if (!ipconfig_ipv4) {
+               connman_error("Service has no IPv4 configuration");
+               return -EINVAL;
+       }
+
+       if (!network->acd_host) {
+               int index;
+
+               index = __connman_ipconfig_get_index(ipconfig_ipv4);
+               network->acd_host = acd_host_new(index,
+                               connman_service_get_dbuspath(service));
+               if (!network->acd_host) {
+                       connman_error("Could not create ACD data structure");
+                       return -EINVAL;
+               }
+
+               acd_host_register_event(network->acd_host,
+                               ACD_HOST_EVENT_IPV4_AVAILABLE,
+                               acd_host_ipv4_available, network);
+               acd_host_register_event(network->acd_host,
+                               ACD_HOST_EVENT_IPV4_LOST,
+                               acd_host_ipv4_lost, network);
+               acd_host_register_event(network->acd_host,
+                               ACD_HOST_EVENT_IPV4_CONFLICT,
+                               acd_host_ipv4_conflict, network);
+               acd_host_register_event(network->acd_host,
+                               ACD_HOST_EVENT_IPV4_MAXCONFLICT,
+                               acd_host_ipv4_maxconflict, network);
+       }
+
+       address = __connman_ipconfig_get_local(ipconfig_ipv4);
+       if (!address)
+               return -EINVAL;
+
+       connman_info("Starting ACD for address %s", address);
+       if (inet_pton(AF_INET, address, &addr) != 1)
+               connman_error("Could not convert address %s", address);
+
+       acd_host_start(network->acd_host, htonl(addr.s_addr));
+
+       return 0;
+}
+
 static void dhcp_success(struct connman_network *network)
 {
        struct connman_service *service;
@@ -163,6 +432,14 @@ static void dhcp_success(struct connman_network *network)
        if (!ipconfig_ipv4)
                return;
 
+       if (connman_setting_get_bool("AddressConflictDetection")) {
+               err = start_acd(network);
+               if (!err)
+                       return;
+
+               /* On error proceed without ACD. */
+       }
+
        err = __connman_ipconfig_address_add(ipconfig_ipv4);
        if (err < 0)
                goto err;
@@ -229,6 +506,14 @@ static int set_connected_manual(struct connman_network *network)
        if (!__connman_ipconfig_get_local(ipconfig))
                __connman_service_read_ip4config(service);
 
+       if (connman_setting_get_bool("AddressConflictDetection")) {
+               err = start_acd(network);
+               if (!err)
+                       return 0;
+
+               /* On error proceed without ACD. */
+       }
+
        err = __connman_ipconfig_address_add(ipconfig);
        if (err < 0)
                goto err;
@@ -241,6 +526,14 @@ err:
        return err;
 }
 
+static void remove_dhcp_timeout(struct connman_network *network)
+{
+       if (network->dhcp_timeout > 0) {
+               g_source_remove(network->dhcp_timeout);
+               network->dhcp_timeout = 0;
+       }
+}
+
 static int set_connected_dhcp(struct connman_network *network)
 {
        struct connman_service *service;
@@ -248,6 +541,7 @@ static int set_connected_dhcp(struct connman_network *network)
        int err;
 
        DBG("network %p", network);
+       remove_dhcp_timeout(network);
 
        service = connman_service_lookup_from_network(network);
        ipconfig_ipv4 = __connman_service_get_ip4config(service);
@@ -263,6 +557,44 @@ static int set_connected_dhcp(struct connman_network *network)
        return 0;
 }
 
+static gboolean set_connected_dhcp_timout(gpointer data)
+{
+       struct connman_network *network = data;
+       struct connman_service *service;
+       struct connman_ipconfig *ipconfig;
+       enum connman_ipconfig_method method;
+
+       network->dhcp_timeout = 0;
+
+       service = connman_service_lookup_from_network(network);
+       if (!service)
+               return FALSE;
+
+       ipconfig = __connman_service_get_ip4config(service);
+       if (!ipconfig)
+               return FALSE;
+
+       /* Method is still DHCP? */
+       method = __connman_ipconfig_get_method(ipconfig);
+       if (method == CONNMAN_IPCONFIG_METHOD_DHCP)
+               set_connected_dhcp(network);
+
+       return FALSE;
+}
+
+void connman_network_set_connected_dhcp_later(struct connman_network *network,
+               uint32_t sec)
+{
+       remove_dhcp_timeout(network);
+
+       network->dhcp_timeout =
+               g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+                               sec,
+                               set_connected_dhcp_timout,
+                               network,
+                               NULL);
+}
+
 static int manual_ipv6_set(struct connman_network *network,
                                struct connman_ipconfig *ipconfig_ipv6)
 {
@@ -427,7 +759,7 @@ static void check_dhcpv6(struct nd_router_advert *reply,
                        DBG("re-send router solicitation %d",
                                                network->router_solicit_count);
                        network->router_solicit_count--;
-                       __connman_inet_ipv6_send_rs(network->index, 1,
+                       __connman_inet_ipv6_send_rs(network->index, RTR_SOLICITATION_INTERVAL,
                                                check_dhcpv6, network);
                        return;
                }
@@ -512,7 +844,6 @@ static void receive_refresh_rs_reply(struct nd_router_advert *reply,
        network->router_solicit_refresh_count = 0;
 
        connman_network_unref(network);
-       return;
 }
 
 int __connman_network_refresh_rs_ipv6(struct connman_network *network,
@@ -577,7 +908,8 @@ static void autoconf_ipv6_set(struct connman_network *network)
 
        /* Try to get stateless DHCPv6 information, RFC 3736 */
        network->router_solicit_count = 3;
-       __connman_inet_ipv6_send_rs(index, 1, check_dhcpv6, network);
+       __connman_inet_ipv6_send_rs(index, RTR_SOLICITATION_INTERVAL,
+                       check_dhcpv6, network);
 }
 
 static void set_connected(struct connman_network *network)
@@ -663,6 +995,7 @@ static void set_disconnected(struct connman_network *network)
                        __connman_service_notify_ipv4_configuration(service);
                        /* fall through */
                case CONNMAN_IPCONFIG_METHOD_DHCP:
+                       remove_dhcp_timeout(network);
                        __connman_dhcp_stop(ipconfig_ipv4);
                        break;
                }
@@ -911,6 +1244,7 @@ static void network_destruct(struct connman_network *network)
        g_free(network->node);
        g_free(network->name);
        g_free(network->identifier);
+       acd_host_free(network->acd_host);
 
        network->device = NULL;
 
@@ -919,7 +1253,7 @@ static void network_destruct(struct connman_network *network)
 
 /**
  * connman_network_create:
- * @identifier: network identifier (for example an unqiue name)
+ * @identifier: network identifier (for example an unique name)
  *
  * Allocate a new network and assign the #identifier to it.
  *
@@ -946,9 +1280,13 @@ struct connman_network *connman_network_create(const char *identifier,
 
        network->type       = type;
        network->identifier = ident;
+       network->acd_host = NULL;
+       network->ipv4ll_timeout = 0;
 
        network_list = g_slist_prepend(network_list, network);
 
+       network->dhcp_timeout = 0;
+
        DBG("network %p identifier %s type %s", network, identifier,
                type2string(type));
        return network;
@@ -1459,10 +1797,10 @@ int __connman_network_connect(struct connman_network *network)
        if (!network->device)
                return -ENODEV;
 
-       network->connecting = true;
-
        __connman_device_disconnect(network->device);
 
+       network->connecting = true;
+
        err = network->driver->connect(network);
        if (err < 0) {
                if (err == -EINPROGRESS)
@@ -1490,6 +1828,10 @@ int __connman_network_disconnect(struct connman_network *network)
 
        DBG("network %p", network);
 
+       remove_ipv4ll_timeout(network);
+       if (network->acd_host)
+               acd_host_stop(network->acd_host);
+
        if (!network->connected && !network->connecting &&
                                                !network->associating)
                return -ENOTCONN;
@@ -1529,13 +1871,16 @@ int __connman_network_clear_ipconfig(struct connman_network *network,
        case CONNMAN_IPCONFIG_METHOD_OFF:
        case CONNMAN_IPCONFIG_METHOD_FIXED:
                return -EINVAL;
-       case CONNMAN_IPCONFIG_METHOD_AUTO:
-               release_dhcpv6(network);
-               break;
        case CONNMAN_IPCONFIG_METHOD_MANUAL:
                __connman_ipconfig_address_remove(ipconfig);
                break;
+       case CONNMAN_IPCONFIG_METHOD_AUTO:
+               release_dhcpv6(network);
+               if (type == CONNMAN_IPCONFIG_TYPE_IPV6)
+                       break;
+               /* fall through */
        case CONNMAN_IPCONFIG_METHOD_DHCP:
+               remove_dhcp_timeout(network);
                __connman_dhcp_stop(ipconfig_ipv4);
                break;
        }
@@ -1919,6 +2264,8 @@ int connman_network_set_bool(struct connman_network *network,
                network->roaming = value;
        else if (g_strcmp0(key, "WiFi.WPS") == 0)
                network->wifi.wps = value;
+       else if (g_strcmp0(key, "WiFi.WPSAdvertising") == 0)
+               network->wifi.wps_advertizing = value;
        else if (g_strcmp0(key, "WiFi.UseWPS") == 0)
                network->wifi.use_wps = value;
 
@@ -1939,6 +2286,8 @@ bool connman_network_get_bool(struct connman_network *network,
                return network->roaming;
        else if (g_str_equal(key, "WiFi.WPS"))
                return network->wifi.wps;
+       else if (g_str_equal(key, "WiFi.WPSAdvertising"))
+               return network->wifi.wps_advertizing;
        else if (g_str_equal(key, "WiFi.UseWPS"))
                return network->wifi.use_wps;