+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;
+
+#if defined TIZEN_EXT
+ err = __connman_ipconfig_gateway_add(ipconfig_ipv4, service);
+#else
+ err = __connman_ipconfig_gateway_add(ipconfig_ipv4);
+#endif
+ 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;
+}
+