static void
ip6_add_domain_search (gpointer data, gpointer user_data)
{
	nm_ip6_config_add_search (NM_IP6_CONFIG (user_data), (const char *) data);
}
static NMIP6Config *
lease_to_ip6_config (const char *iface,
                     int ifindex,
                     sd_dhcp6_lease *lease,
                     GHashTable *options,
                     gboolean log_lease,
                     gboolean info_only,
                     GError **error)
{
	struct in6_addr tmp_addr, *dns;
	uint32_t lft_pref, lft_valid;
	NMIP6Config *ip6_config;
	const char *addr_str;
	char **domains;
	GString *str;
	int num, i;
	gint32 ts;

	g_return_val_if_fail (lease, NULL);
	ip6_config = nm_ip6_config_new (ifindex);
	ts = nm_utils_get_monotonic_timestamp_s ();
	str = g_string_sized_new (30);

	/* Addresses */
	sd_dhcp6_lease_reset_address_iter (lease);
	while (sd_dhcp6_lease_get_address (lease, &tmp_addr, &lft_pref, &lft_valid) >= 0) {
		NMPlatformIP6Address address = {
			.plen = 128,
			.address = tmp_addr,
			.timestamp = ts,
			.lifetime = lft_valid,
			.preferred = lft_pref,
			.addr_source = NM_IP_CONFIG_SOURCE_DHCP,
		};

		nm_ip6_config_add_address (ip6_config, &address);

		addr_str = nm_utils_inet6_ntop (&tmp_addr, NULL);
		g_string_append_printf (str, "%s%s", str->len ? " " : "", addr_str);

		LOG_LEASE (LOGD_DHCP6,
		           "  address %s",
		           nm_platform_ip6_address_to_string (&address, NULL, 0));
	};

	if (str->len) {
		add_option (options, dhcp6_requests, DHCP6_OPTION_IP_ADDRESS, str->str);
		g_string_set_size (str , 0);
	}

	if (!info_only && nm_ip6_config_get_num_addresses (ip6_config) == 0) {
		g_string_free (str, TRUE);
		g_object_unref (ip6_config);
		g_set_error_literal (error,
		                     NM_MANAGER_ERROR,
		                     NM_MANAGER_ERROR_FAILED,
		                     "no address received in managed mode");
		return NULL;
	}

	/* DNS servers */
	num = sd_dhcp6_lease_get_dns (lease, &dns);
	if (num > 0) {
		for (i = 0; i < num; i++) {
			nm_ip6_config_add_nameserver (ip6_config, &dns[i]);
			addr_str = nm_utils_inet6_ntop (&dns[i], NULL);
			g_string_append_printf (str, "%s%s", str->len ? " " : "", addr_str);
			LOG_LEASE (LOGD_DHCP6, "  nameserver %s", addr_str);
		}
		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DNS_SERVERS, str->str);
		g_string_set_size (str, 0);
	}

	/* Search domains */
	num = sd_dhcp6_lease_get_domains (lease, &domains);
	if (num > 0) {
		for (i = 0; i < num; i++) {
			nm_ip6_config_add_search (ip6_config, domains[i]);
			g_string_append_printf (str, "%s%s", str->len ? " " : "", domains[i]);
			LOG_LEASE (LOGD_DHCP6, "  domain name '%s'", domains[i]);
		}
		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DOMAIN_LIST, str->str);
		g_string_set_size (str, 0);
	}

	g_string_free (str, TRUE);

	return ip6_config;
}

static void
bound6_handle (NMDhcpSystemd *self)
{
	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
	const char *iface = nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self));
	gs_unref_object NMIP6Config *ip6_config = NULL;
	gs_unref_hashtable GHashTable *options = NULL;
	gs_free_error GError *error = NULL;
	sd_dhcp6_lease *lease;
	int r;

	r = sd_dhcp6_client_get_lease (priv->client6, &lease);
	if (r < 0 || !lease) {
		_LOGW (" no lease!");
		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
		return;
	}

	_LOGD ("lease available");

	options = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
	ip6_config = lease_to_ip6_config (iface,
	                                  nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)),
	                                  lease,
	                                  options,
	                                  TRUE,
	                                  priv->info_only,
	                                  &error);

	if (ip6_config) {
		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
		                          NM_DHCP_STATE_BOUND,
		                          G_OBJECT (ip6_config),
		                          options);
	} else {
		_LOGW ("%s", error->message);
		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
	}
}
/* This is exactly identical to nm_utils_merge_ip4_config, with s/4/6/,
 * except that we can't compare addresses with ==.
 */
void
nm_utils_merge_ip6_config (NMIP6Config *ip6_config, NMSettingIP6Config *setting)
{
	int i, j;

	if (!setting)
		return; /* Defaults are just fine */

	if (nm_setting_ip6_config_get_ignore_auto_dns (setting)) {
		nm_ip6_config_reset_nameservers (ip6_config);
		nm_ip6_config_reset_domains (ip6_config);
		nm_ip6_config_reset_searches (ip6_config);
	}

	if (nm_setting_ip6_config_get_ignore_auto_routes (setting))
		nm_ip6_config_reset_routes (ip6_config);

	for (i = 0; i < nm_setting_ip6_config_get_num_dns (setting); i++) {
		const struct in6_addr *ns;
		gboolean found = FALSE;

		/* Avoid dupes */
		ns = nm_setting_ip6_config_get_dns (setting, i);
		for (j = 0; j < nm_ip6_config_get_num_nameservers (ip6_config); j++) {
			if (ip6_addresses_equal (nm_ip6_config_get_nameserver (ip6_config, j), ns)) {
				found = TRUE;
				break;
			}
		}

		if (!found)
			nm_ip6_config_add_nameserver (ip6_config, ns);
	}

	/* DNS search domains */
	for (i = 0; i < nm_setting_ip6_config_get_num_dns_searches (setting); i++) {
		const char *search = nm_setting_ip6_config_get_dns_search (setting, i);
		gboolean found = FALSE;

		/* Avoid dupes */
		for (j = 0; j < nm_ip6_config_get_num_searches (ip6_config); j++) {
			if (!strcmp (search, nm_ip6_config_get_search (ip6_config, j))) {
				found = TRUE;
				break;
			}
		}

		if (!found)
			nm_ip6_config_add_search (ip6_config, search);
	}

	/* IPv6 addresses */
	for (i = 0; i < nm_setting_ip6_config_get_num_addresses (setting); i++) {
		NMIP6Address *setting_addr = nm_setting_ip6_config_get_address (setting, i);
		guint32 num;

		num = nm_ip6_config_get_num_addresses (ip6_config);
		for (j = 0; j < num; j++) {
			NMIP6Address *cfg_addr = nm_ip6_config_get_address (ip6_config, j);

			/* Dupe, override with user-specified address */
			if (ip6_addresses_equal (nm_ip6_address_get_address (cfg_addr), nm_ip6_address_get_address (setting_addr))) {
				nm_ip6_config_replace_address (ip6_config, j, setting_addr);
				break;
			}
		}

		if (j == num)
			nm_ip6_config_add_address (ip6_config, setting_addr);
	}

	/* IPv6 routes */
	for (i = 0; i < nm_setting_ip6_config_get_num_routes (setting); i++) {
		NMIP6Route *setting_route = nm_setting_ip6_config_get_route (setting, i);
		guint32 num;

		num = nm_ip6_config_get_num_routes (ip6_config);
		for (j = 0; j < num; j++) {
			NMIP6Route *cfg_route = nm_ip6_config_get_route (ip6_config, j);

			/* Dupe, override with user-specified route */
			if (   ip6_addresses_equal (nm_ip6_route_get_dest (cfg_route), nm_ip6_route_get_dest (setting_route))
			    && (nm_ip6_route_get_prefix (cfg_route) == nm_ip6_route_get_prefix (setting_route))
				&& ip6_addresses_equal (nm_ip6_route_get_next_hop (cfg_route), nm_ip6_route_get_next_hop (setting_route))) {
				nm_ip6_config_replace_route (ip6_config, j, setting_route);
				break;
			}
		}

		if (j == num)
			nm_ip6_config_add_route (ip6_config, setting_route);
	}

	if (nm_setting_ip6_config_get_never_default (setting))
		nm_ip6_config_set_never_default (ip6_config, TRUE);
}