void
nm_dhcp_client_stop (NMDHCPClient *self, gboolean release)
{
	NMDHCPClientPrivate *priv;

	g_return_if_fail (self != NULL);
	g_return_if_fail (NM_IS_DHCP_CLIENT (self));

	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);

	/* Kill the DHCP client */
	if (!priv->dead) {
		NM_DHCP_CLIENT_GET_CLASS (self)->stop (self, release);
		priv->dead = TRUE;

		nm_log_info (LOGD_DHCP, "(%s): canceled DHCP transaction, DHCP client pid %d",
		             priv->iface, priv->pid);
	}

	/* And clean stuff up */

	priv->pid = -1;
	dhcp_client_set_state (self, DHC_END, FALSE, TRUE);

	g_hash_table_remove_all (priv->options);

	timeout_cleanup (self);
	watch_cleanup (self);
}
static void
set_property (GObject *object, guint prop_id,
			  const GValue *value, GParamSpec *pspec)
{
	NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (object);
 
	switch (prop_id) {
	case PROP_IFACE:
		/* construct-only */
		priv->iface = g_strdup (g_value_get_string (value));
		break;
	case PROP_IPV6:
		/* construct-only */
		priv->ipv6 = g_value_get_boolean (value);
		break;
	case PROP_UUID:
		/* construct-only */
		priv->uuid = g_value_dup_string (value);
		break;
	case PROP_TIMEOUT:
		priv->timeout = g_value_get_uint (value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
gboolean
nm_dhcp_client_start_ip6 (NMDHCPClient *self,
                          NMSettingIP6Config *s_ip6,
                          guint8 *dhcp_anycast_addr,
                          const char *hostname,
                          gboolean info_only)
{
	NMDHCPClientPrivate *priv;

	g_return_val_if_fail (self != NULL, FALSE);
	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);

	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
	g_return_val_if_fail (priv->pid == -1, FALSE);
	g_return_val_if_fail (priv->ipv6 == TRUE, FALSE);
	g_return_val_if_fail (priv->uuid != NULL, FALSE);

	priv->info_only = info_only;

	nm_log_info (LOGD_DHCP, "Activation (%s) Beginning DHCPv6 transaction (timeout in %d seconds)",
	             priv->iface, priv->timeout);

	priv->pid = NM_DHCP_CLIENT_GET_CLASS (self)->ip6_start (self, s_ip6, dhcp_anycast_addr, hostname, info_only);
	if (priv->pid > 0)
		start_monitor (self);

	return priv->pid ? TRUE : FALSE;
}
static void
daemon_watch_cb (GPid pid, gint status, gpointer user_data)
{
	NMDHCPClient *self = NM_DHCP_CLIENT (user_data);
	NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
	NMDHCPState new_state;

	if (priv->ipv6) {
		nm_log_info (LOGD_DHCP6, "(%s): DHCPv6 client pid %d exited with status %d",
		             priv->iface, pid,
		             WIFEXITED (status) ? WEXITSTATUS (status) : -1);
	} else {
		nm_log_info (LOGD_DHCP4, "(%s): DHCPv4 client pid %d exited with status %d",
		             priv->iface, pid,
		             WIFEXITED (status) ? WEXITSTATUS (status) : -1);
	}

	if (!WIFEXITED (status)) {
		new_state = DHC_ABEND;
		nm_log_warn (LOGD_DHCP, "DHCP client died abnormally");
	} else
		new_state = DHC_END;

	watch_cleanup (self);
	timeout_cleanup (self);
	priv->dead = TRUE;

	dhcp_client_set_state (self, new_state, TRUE, FALSE);
}
static void
get_property (GObject *object, guint prop_id,
			  GValue *value, GParamSpec *pspec)
{
	NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (object);

	switch (prop_id) {
	case PROP_IFACE:
		g_value_set_string (value, priv->iface);
		break;
	case PROP_HWADDR:
		g_value_set_boxed (value, priv->hwaddr);
		break;
	case PROP_IPV6:
		g_value_set_boolean (value, priv->ipv6);
		break;
	case PROP_UUID:
		g_value_set_string (value, priv->uuid);
		break;
	case PROP_TIMEOUT:
		g_value_set_uint (value, priv->timeout);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
static void
nm_dhcp_client_init (NMDHCPClient *self)
{
	NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);

	priv->options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
	priv->pid = -1;
}
GPid
nm_dhcp_client_get_pid (NMDHCPClient *self)
{
	g_return_val_if_fail (self != NULL, -1);
	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), -1);

	return NM_DHCP_CLIENT_GET_PRIVATE (self)->pid;
}
const char *
nm_dhcp_client_get_iface (NMDHCPClient *self)
{
	g_return_val_if_fail (self != NULL, NULL);
	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);

	return NM_DHCP_CLIENT_GET_PRIVATE (self)->iface;
}
gboolean
nm_dhcp_client_get_ipv6 (NMDHCPClient *self)
{
	g_return_val_if_fail (self != NULL, FALSE);
	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);

	return NM_DHCP_CLIENT_GET_PRIVATE (self)->ipv6;
}
static gboolean
signal_remove (gpointer user_data)
{
	NMDHCPClient *self = NM_DHCP_CLIENT (user_data);

	NM_DHCP_CLIENT_GET_PRIVATE (self)->remove_id = 0;
	g_signal_emit (G_OBJECT (self), signals[REMOVE], 0);
	return FALSE;
}
void
nm_dhcp_client_new_options (NMDHCPClient *self,
                            GHashTable *options,
                            const char *reason)
{
	NMDHCPClientPrivate *priv;
	guint32 old_state;
	guint32 new_state;

	g_return_if_fail (self != NULL);
	g_return_if_fail (NM_IS_DHCP_CLIENT (self));
	g_return_if_fail (options != NULL);
	g_return_if_fail (reason != NULL);

	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
	old_state = priv->state;
	new_state = string_to_state (reason);

	/* Clear old and save new DHCP options */
	g_hash_table_remove_all (priv->options);
	g_hash_table_foreach (options, copy_option, priv->options);

	if (old_state == new_state) {
		/* dhclient will stay in the same state (or, really, provide the same
		 * reason) for operations like RENEW and REBIND.  We need to ensure
		 * that triggers various DHCP lease change code, so we need to pass
		 * along same-state transitions for these states.
		 */
		if (   new_state != DHC_BOUND4
		    && new_state != DHC_RENEW4
		    && new_state != DHC_REBIND4
		    && new_state != DHC_BOUND6
		    && new_state != DHC_RENEW6
		    && new_state != DHC_REBIND6)
			return;
	}

	/* Handle changed device state */
	if (state_is_bound (new_state)) {
		/* Cancel the timeout if the DHCP client is now bound */
		timeout_cleanup (self);
	}

	if (priv->ipv6) {
		nm_log_info (LOGD_DHCP6, "(%s): DHCPv6 state changed %s -> %s",
		            priv->iface,
		            state_to_string (old_state),
		            state_to_string (new_state));
	} else {
		nm_log_info (LOGD_DHCP4, "(%s): DHCPv4 state changed %s -> %s",
		            priv->iface,
		            state_to_string (old_state),
		            state_to_string (new_state));
	}

	dhcp_client_set_state (self, new_state, TRUE, FALSE);
}
static void
watch_cleanup (NMDHCPClient *self)
{
	NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);

	if (priv->watch_id) {
		g_source_remove (priv->watch_id);
		priv->watch_id = 0;
	}
}
gboolean
nm_dhcp_client_foreach_option (NMDHCPClient *self,
                               GHFunc func,
                               gpointer user_data)
{
	NMDHCPClientPrivate *priv;
	GHashTableIter iter;
	gpointer iterkey, itervalue;

	g_return_val_if_fail (self != NULL, FALSE);
	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
	g_return_val_if_fail (func != NULL, FALSE);

	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);

	if (!state_is_bound (priv->state)) {
		if (priv->ipv6) {
			nm_log_warn (LOGD_DHCP6, "(%s): DHCPv6 client didn't bind to a lease.", priv->iface);
		} else {
			nm_log_warn (LOGD_DHCP4, "(%s): DHCPv4 client didn't bind to a lease.", priv->iface);
		}
	}

	g_hash_table_iter_init (&iter, priv->options);
	while (g_hash_table_iter_next (&iter, &iterkey, &itervalue)) {
		const char *key = iterkey, *value = itervalue;
		const char **p;
		static const char *filter_options[] = {
			"interface", "pid", "reason", "dhcp_message_type", NULL
		};
		gboolean ignore = FALSE;

		/* Filter out stuff that's not actually new DHCP options */
		for (p = filter_options; *p; p++) {
			if (!strcmp (*p, key) || !strncmp (key, OLD_TAG, strlen (OLD_TAG))) {
				ignore = TRUE;
				break;
			}
		}

		if (!ignore) {
			const char *tmp_key = key;

			/* Remove the "new_" prefix that dhclient passes back */
			if (!strncmp (key, NEW_TAG, strlen (NEW_TAG)))
				tmp_key = key + strlen (NEW_TAG);

			func ((gpointer) tmp_key, (gpointer) value, user_data);
		}
	}
	return TRUE;
}
static gboolean
daemon_timeout (gpointer user_data)
{
	NMDHCPClient *self = NM_DHCP_CLIENT (user_data);
	NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);

	if (priv->ipv6) {
		nm_log_warn (LOGD_DHCP6, "(%s): DHCPv6 request timed out.", priv->iface);
	} else {
		nm_log_warn (LOGD_DHCP4, "(%s): DHCPv4 request timed out.", priv->iface);
	}
	g_signal_emit (G_OBJECT (self), signals[TIMEOUT], 0);
	return FALSE;
}
static void
start_monitor (NMDHCPClient *self)
{
	NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);

	g_return_if_fail (priv->pid > 0);

	/* Set up a timeout on the transaction to kill it after the timeout */
	priv->timeout_id = g_timeout_add_seconds (priv->timeout,
	                                          daemon_timeout,
	                                          self);
	priv->watch_id = g_child_watch_add (priv->pid,
	                                    (GChildWatchFunc) daemon_watch_cb,
	                                    self);
}
gboolean
nm_dhcp_client_start_ip6 (NMDHCPClient *self,
                          NMSettingIP6Config *s_ip6,
                          guint8 *dhcp_anycast_addr,
                          const char *hostname,
                          gboolean info_only)
{
	NMDHCPClientPrivate *priv;
	char *escaped;

	g_return_val_if_fail (self != NULL, FALSE);
	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);

	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
	g_return_val_if_fail (priv->pid == -1, FALSE);
	g_return_val_if_fail (priv->ipv6 == TRUE, FALSE);
	g_return_val_if_fail (priv->uuid != NULL, FALSE);

	/* If we don't have one yet, read the default DUID for this DHCPv6 client
	 * from the client-specific persistent configuration.
	 */
	if (!priv->duid)
		priv->duid = NM_DHCP_CLIENT_GET_CLASS (self)->get_duid (self);

	if (nm_logging_level_enabled (LOGL_DEBUG)) {
		escaped = escape_duid (priv->duid);
		nm_log_dbg (LOGD_DHCP, "(%s): DHCPv6 DUID is '%s'", priv->iface, escaped);
		g_free (escaped);
	}

	priv->info_only = info_only;

	nm_log_info (LOGD_DHCP, "Activation (%s) Beginning DHCPv6 transaction (timeout in %d seconds)",
	             priv->iface, priv->timeout);

	priv->pid = NM_DHCP_CLIENT_GET_CLASS (self)->ip6_start (self,
	                                                        s_ip6,
	                                                        dhcp_anycast_addr,
	                                                        hostname,
	                                                        info_only,
	                                                        priv->duid);
	if (priv->pid > 0)
		start_monitor (self);

	return priv->pid ? TRUE : FALSE;
}
예제 #17
0
void
nm_dhcp_client_new_options (NMDHCPClient *self,
                            GHashTable *options,
                            const char *reason)
{
	NMDHCPClientPrivate *priv;
	guint32 old_state;
	guint32 new_state;

	g_return_if_fail (self != NULL);
	g_return_if_fail (NM_IS_DHCP_CLIENT (self));
	g_return_if_fail (options != NULL);
	g_return_if_fail (reason != NULL);

	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
	old_state = priv->state;
	new_state = string_to_state (reason);

	/* Clear old and save new DHCP options */
	g_hash_table_remove_all (priv->options);
	g_hash_table_foreach (options, copy_option, priv->options);

	if (old_state == new_state)
		return;

	/* Handle changed device state */
	if (state_is_bound (new_state)) {
		/* Cancel the timeout if the DHCP client is now bound */
		timeout_cleanup (self);
	}

	if (priv->ipv6) {
		nm_log_info (LOGD_DHCP6, "(%s): DHCPv6 state changed %s -> %s",
		            priv->iface,
		            state_to_string (old_state),
		            state_to_string (new_state));
	} else {
		nm_log_info (LOGD_DHCP4, "(%s): DHCPv4 state changed %s -> %s",
		            priv->iface,
		            state_to_string (old_state),
		            state_to_string (new_state));
	}

	dhcp_client_set_state (self, new_state, TRUE, FALSE);
}
static void
stop (NMDHCPClient *self, gboolean release)
{
	NMDHCPClientPrivate *priv;

	g_return_if_fail (self != NULL);
	g_return_if_fail (NM_IS_DHCP_CLIENT (self));

	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
	g_return_if_fail (priv->pid > 0);

	/* Clean up the watch handler since we're explicitly killing the daemon */
	watch_cleanup (self);

	nm_dhcp_client_stop_pid (priv->pid, priv->iface, 0);

	priv->info_only = FALSE;
}
static void
dispose (GObject *object)
{
	NMDHCPClient *self = NM_DHCP_CLIENT (object);
	NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);

	/* Stopping the client is left up to the controlling device
	 * explicitly since we may want to quit NetworkManager but not terminate
	 * the DHCP client.
	 */

	if (priv->remove_id)
		g_source_remove (priv->remove_id);

	g_hash_table_destroy (priv->options);
	g_free (priv->iface);

	G_OBJECT_CLASS (nm_dhcp_client_parent_class)->dispose (object);
}
NMIP6Config *
nm_dhcp_client_get_ip6_config (NMDHCPClient *self, gboolean test)
{
	NMDHCPClientPrivate *priv;

	g_return_val_if_fail (self != NULL, NULL);
	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);

	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);

	if (test && !state_is_bound (priv->state)) {
		nm_log_warn (LOGD_DHCP6, "(%s): DHCPv6 client didn't bind to a lease.", priv->iface);
		return NULL;
	}

	if (!g_hash_table_size (priv->options)) {
		/* We never got a response from the DHCP client */
		return NULL;
	}

	return ip6_options_to_config (self);
}
static void
dhcp_client_set_state (NMDHCPClient *self,
                       NMDHCPState state,
                       gboolean emit_state,
                       gboolean remove_now)
{
	NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);

	priv->state = state;

	if (emit_state)
		g_signal_emit (G_OBJECT (self), signals[STATE_CHANGED], 0, priv->state);

	if (state == DHC_END || state == DHC_ABEND) {
		/* Start the remove signal timer */
		if (remove_now) {
			g_signal_emit (G_OBJECT (self), signals[REMOVE], 0);
		} else {
			if (!priv->remove_id)
				priv->remove_id = g_timeout_add_seconds (5, signal_remove, self);
		}
	}
}
/* Given a table of DHCP options from the client, convert into an IP4Config */
static NMIP4Config *
ip4_options_to_config (NMDHCPClient *self)
{
	NMDHCPClientPrivate *priv;
	NMIP4Config *ip4_config = NULL;
	struct in_addr tmp_addr;
	NMIP4Address *addr = NULL;
	char *str = NULL;
	guint32 gwaddr = 0, prefix = 0;

	g_return_val_if_fail (self != NULL, NULL);
	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);

	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
	g_return_val_if_fail (priv->options != NULL, NULL);

	ip4_config = nm_ip4_config_new ();
	if (!ip4_config) {
		nm_log_warn (LOGD_DHCP4, "(%s): couldn't allocate memory for an IP4Config!", priv->iface);
		return NULL;
	}

	addr = nm_ip4_address_new ();
	if (!addr) {
		nm_log_warn (LOGD_DHCP4, "(%s): couldn't allocate memory for an IP4 Address!", priv->iface);
		goto error;
	}

	str = g_hash_table_lookup (priv->options, "new_ip_address");
	if (str && (inet_pton (AF_INET, str, &tmp_addr) > 0)) {
		nm_ip4_address_set_address (addr, tmp_addr.s_addr);
		nm_log_info (LOGD_DHCP4, "  address %s", str);
	} else
		goto error;

	str = g_hash_table_lookup (priv->options, "new_subnet_mask");
	if (str && (inet_pton (AF_INET, str, &tmp_addr) > 0)) {
		prefix = nm_utils_ip4_netmask_to_prefix (tmp_addr.s_addr);
		nm_log_info (LOGD_DHCP4, "  prefix %d (%s)", prefix, str);
	} else {
		/* Get default netmask for the IP according to appropriate class. */
		prefix = nm_utils_ip4_get_default_prefix (nm_ip4_address_get_address (addr));
		nm_log_info (LOGD_DHCP4, "  prefix %d (default)", prefix);
	}
	nm_ip4_address_set_prefix (addr, prefix);

	/* Routes: if the server returns classless static routes, we MUST ignore
	 * the 'static_routes' option.
	 */
	if (!ip4_process_classless_routes (priv->options, ip4_config, &gwaddr))
		process_classful_routes (priv->options, ip4_config);

	if (gwaddr) {
		char buf[INET_ADDRSTRLEN + 1];

		inet_ntop (AF_INET, &gwaddr, buf, sizeof (buf));
		nm_log_info (LOGD_DHCP4, "  gateway %s", buf);
		nm_ip4_address_set_gateway (addr, gwaddr);
	} else {
		/* If the gateway wasn't provided as a classless static route with a
		 * subnet length of 0, try to find it using the old-style 'routers' option.
		 */
		str = g_hash_table_lookup (priv->options, "new_routers");
		if (str) {
			char **routers = g_strsplit (str, " ", 0);
			char **s;

			for (s = routers; *s; s++) {
				/* FIXME: how to handle multiple routers? */
				if (inet_pton (AF_INET, *s, &tmp_addr) > 0) {
					nm_ip4_address_set_gateway (addr, tmp_addr.s_addr);
					nm_log_info (LOGD_DHCP4, "  gateway %s", *s);
					break;
				} else
					nm_log_warn (LOGD_DHCP4, "ignoring invalid gateway '%s'", *s);
			}
			g_strfreev (routers);
		}
	}

	nm_ip4_config_take_address (ip4_config, addr);
	addr = NULL;

	str = g_hash_table_lookup (priv->options, "new_host_name");
	if (str)
		nm_log_info (LOGD_DHCP4, "  hostname '%s'", str);

	str = g_hash_table_lookup (priv->options, "new_domain_name_servers");
	if (str) {
		char **searches = g_strsplit (str, " ", 0);
		char **s;

		for (s = searches; *s; s++) {
			if (inet_pton (AF_INET, *s, &tmp_addr) > 0) {
				nm_ip4_config_add_nameserver (ip4_config, tmp_addr.s_addr);
				nm_log_info (LOGD_DHCP4, "  nameserver '%s'", *s);
			} else
				nm_log_warn (LOGD_DHCP4, "ignoring invalid nameserver '%s'", *s);
		}
		g_strfreev (searches);
	}

	str = g_hash_table_lookup (priv->options, "new_domain_name");
	if (str) {
		char **domains = g_strsplit (str, " ", 0);
		char **s;

		for (s = domains; *s; s++) {
			nm_log_info (LOGD_DHCP4, "  domain name '%s'", *s);
			nm_ip4_config_add_domain (ip4_config, *s);
		}
		g_strfreev (domains);
	}

	str = g_hash_table_lookup (priv->options, "new_domain_search");
	if (str)
		process_domain_search (str, ip4_add_domain_search, ip4_config);

	str = g_hash_table_lookup (priv->options, "new_netbios_name_servers");
	if (str) {
		char **searches = g_strsplit (str, " ", 0);
		char **s;

		for (s = searches; *s; s++) {
			if (inet_pton (AF_INET, *s, &tmp_addr) > 0) {
				nm_ip4_config_add_wins (ip4_config, tmp_addr.s_addr);
				nm_log_info (LOGD_DHCP4, "  wins '%s'", *s);
			} else
				nm_log_warn (LOGD_DHCP4, "ignoring invalid WINS server '%s'", *s);
		}
		g_strfreev (searches);
	}

	str = g_hash_table_lookup (priv->options, "new_interface_mtu");
	if (str) {
		int int_mtu;

		errno = 0;
		int_mtu = strtol (str, NULL, 10);
		if ((errno == EINVAL) || (errno == ERANGE))
			goto error;

		if (int_mtu > 576)
			nm_ip4_config_set_mtu (ip4_config, int_mtu);
	}

	str = g_hash_table_lookup (priv->options, "new_nis_domain");
	if (str) {
		nm_log_info (LOGD_DHCP4, "  NIS domain '%s'", str);
		nm_ip4_config_set_nis_domain (ip4_config, str);
	}

	str = g_hash_table_lookup (priv->options, "new_nis_servers");
	if (str) {
		char **searches = g_strsplit (str, " ", 0);
		char **s;

		for (s = searches; *s; s++) {
			if (inet_pton (AF_INET, *s, &tmp_addr) > 0) {
				nm_ip4_config_add_nis_server (ip4_config, tmp_addr.s_addr);
				nm_log_info (LOGD_DHCP4, "  nis '%s'", *s);
			} else
				nm_log_warn (LOGD_DHCP4, "ignoring invalid NIS server '%s'", *s);
		}
		g_strfreev (searches);
	}

	return ip4_config;

error:
	if (addr)
		nm_ip4_address_unref (addr);
	g_object_unref (ip4_config);
	return NULL;
}
예제 #23
0
/* Given a table of DHCP options from the client, convert into an IP6Config */
static NMIP6Config *
ip6_options_to_config (NMDHCPClient *self)
{
	NMDHCPClientPrivate *priv;
	NMIP6Config *ip6_config = NULL;
	struct in6_addr tmp_addr;
	NMIP6Address *addr = NULL;
	char *str = NULL;
	GHashTableIter iter;
	gpointer key, value;

	g_return_val_if_fail (self != NULL, NULL);
	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);

	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
	g_return_val_if_fail (priv->options != NULL, NULL);

	g_hash_table_iter_init (&iter, priv->options);
	while (g_hash_table_iter_next (&iter, &key, &value)) {
		nm_log_dbg (LOGD_DHCP6, "(%s): option '%s'=>'%s'",
		            priv->iface, (const char *) key, (const char *) value);
	}

	ip6_config = nm_ip6_config_new ();
	if (!ip6_config) {
		nm_log_warn (LOGD_DHCP6, "(%s): couldn't allocate memory for an IP6Config!", priv->iface);
		return NULL;
	}

	addr = nm_ip6_address_new ();
	if (!addr) {
		nm_log_warn (LOGD_DHCP6, "(%s): couldn't allocate memory for an IP6 Address!", priv->iface);
		goto error;
	}

	str = g_hash_table_lookup (priv->options, "new_ip6_address");
	if (str) {
		if (!inet_pton (AF_INET6, str, &tmp_addr)) {
			nm_log_warn (LOGD_DHCP6, "(%s): DHCP returned invalid address '%s'",
			             priv->iface);
			goto error;
		}

		nm_ip6_address_set_address (addr, &tmp_addr);
		nm_log_info (LOGD_DHCP6, "  address %s", str);
	} else {
		/* No address in managed mode is a hard error */
		if (priv->info_only == FALSE)
			goto error;

		/* But "info-only" setups don't necessarily need an address */
		nm_ip6_address_unref (addr);
		addr = NULL;
	}

	/* Only care about prefix if we got an address */
	if (addr) {
		str = g_hash_table_lookup (priv->options, "new_ip6_prefixlen");
		if (str) {
			long unsigned int prefix;

			errno = 0;
			prefix = strtoul (str, NULL, 10);
			if (errno != 0 || prefix > 128)
				goto error;

			nm_ip6_address_set_prefix (addr, (guint32) prefix);
			nm_log_info (LOGD_DHCP6, "  prefix %lu", prefix);
		}

		nm_ip6_config_take_address (ip6_config, addr);
		addr = NULL;
	}

	str = g_hash_table_lookup (priv->options, "new_host_name");
	if (str)
		nm_log_info (LOGD_DHCP6, "  hostname '%s'", str);

	str = g_hash_table_lookup (priv->options, "new_dhcp6_name_servers");
	if (str) {
		char **searches = g_strsplit (str, " ", 0);
		char **s;

		for (s = searches; *s; s++) {
			if (inet_pton (AF_INET6, *s, &tmp_addr) > 0) {
				nm_ip6_config_add_nameserver (ip6_config, &tmp_addr);
				nm_log_info (LOGD_DHCP6, "  nameserver '%s'", *s);
			} else
				nm_log_warn (LOGD_DHCP6, "ignoring invalid nameserver '%s'", *s);
		}
		g_strfreev (searches);
	}

	str = g_hash_table_lookup (priv->options, "new_dhcp6_domain_search");
	if (str)
		process_domain_search (str, ip6_add_domain_search, ip6_config);

	return ip6_config;

error:
	if (addr)
		nm_ip6_address_unref (addr);
	g_object_unref (ip6_config);
	return NULL;
}
/* Given a table of DHCP options from the client, convert into an IP6Config */
static NMIP6Config *
ip6_options_to_config (NMDHCPClient *self)
{
	NMDHCPClientPrivate *priv;
	NMIP6Config *ip6_config = NULL;
	struct in6_addr tmp_addr;
	NMIP6Address *addr = NULL;
	char *str = NULL;
	GHashTableIter iter;
	gpointer key, value;

	g_return_val_if_fail (self != NULL, NULL);
	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);

	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
	g_return_val_if_fail (priv->options != NULL, NULL);

	g_hash_table_iter_init (&iter, priv->options);
	while (g_hash_table_iter_next (&iter, &key, &value)) {
		nm_log_dbg (LOGD_DHCP6, "(%s): option '%s'=>'%s'",
		            priv->iface, (const char *) key, (const char *) value);
	}

	ip6_config = nm_ip6_config_new ();
	if (!ip6_config) {
		nm_log_warn (LOGD_DHCP6, "(%s): couldn't allocate memory for an IP6Config!", priv->iface);
		return NULL;
	}

	str = g_hash_table_lookup (priv->options, "new_ip6_address");
	if (str) {
		if (!inet_pton (AF_INET6, str, &tmp_addr)) {
			nm_log_warn (LOGD_DHCP6, "(%s): DHCP returned invalid address '%s'",
			             priv->iface, str);
			goto error;
		}

		addr = nm_ip6_address_new ();
		g_assert (addr);
		nm_ip6_address_set_address (addr, &tmp_addr);
		/* DHCPv6 IA_NA assignments are single address only */
		nm_ip6_address_set_prefix (addr, 128);
		nm_log_info (LOGD_DHCP6, "  address %s/128", str);

		nm_ip6_config_take_address (ip6_config, addr);
	} else if (priv->info_only == FALSE) {
		/* No address in Managed mode is a hard error */
		goto error;
	}

	str = g_hash_table_lookup (priv->options, "new_host_name");
	if (str)
		nm_log_info (LOGD_DHCP6, "  hostname '%s'", str);

	str = g_hash_table_lookup (priv->options, "new_dhcp6_name_servers");
	if (str) {
		char **searches = g_strsplit (str, " ", 0);
		char **s;

		for (s = searches; *s; s++) {
			if (inet_pton (AF_INET6, *s, &tmp_addr) > 0) {
				nm_ip6_config_add_nameserver (ip6_config, &tmp_addr);
				nm_log_info (LOGD_DHCP6, "  nameserver '%s'", *s);
			} else
				nm_log_warn (LOGD_DHCP6, "ignoring invalid nameserver '%s'", *s);
		}
		g_strfreev (searches);
	}

	str = g_hash_table_lookup (priv->options, "new_dhcp6_domain_search");
	if (str)
		process_domain_search (str, ip6_add_domain_search, ip6_config);

	return ip6_config;

error:
	g_object_unref (ip6_config);
	return NULL;
}