NetworkManager/src/dhcp-manager/nm-dhcp-client.c
Thomas Haller f56c82d861 core/dhcp: reduce the wait time when killing dhcp client process
We kill the dhcp process synchronously, so waiting for up to 3 seconds
is really painful. Instead, give the client only 0.5 to terminate before
sending SIGKILL.

The proper solution would be to kill it asynchronously and dhcp manager
making sure that it does not start a new instance before the old process
was killed.

Signed-off-by: Thomas Haller <thaller@redhat.com>
2014-03-06 19:03:27 +01:00

1666 lines
44 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2005 - 2010 Red Hat, Inc.
*
*/
#include <config.h>
#include <glib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <uuid/uuid.h>
#include "NetworkManagerUtils.h"
#include "nm-utils.h"
#include "nm-logging.h"
#include "nm-dbus-glib-types.h"
#include "nm-dhcp-client.h"
typedef struct {
char * iface;
GByteArray * hwaddr;
gboolean ipv6;
char * uuid;
guint32 timeout;
GByteArray * duid;
guchar state;
GPid pid;
gboolean dead;
guint timeout_id;
guint watch_id;
guint32 remove_id;
GHashTable * options;
gboolean info_only;
} NMDHCPClientPrivate;
#define NM_DHCP_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DHCP_CLIENT, NMDHCPClientPrivate))
G_DEFINE_TYPE_EXTENDED (NMDHCPClient, nm_dhcp_client, G_TYPE_OBJECT, G_TYPE_FLAG_ABSTRACT, {})
enum {
STATE_CHANGED,
TIMEOUT,
REMOVE,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
enum {
PROP_0,
PROP_IFACE,
PROP_HWADDR,
PROP_IPV6,
PROP_UUID,
PROP_TIMEOUT,
LAST_PROP
};
/********************************************/
GPid
nm_dhcp_client_get_pid (NMDHCPClient *self)
{
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 (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 (NM_IS_DHCP_CLIENT (self), FALSE);
return NM_DHCP_CLIENT_GET_PRIVATE (self)->ipv6;
}
const char *
nm_dhcp_client_get_uuid (NMDHCPClient *self)
{
g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
return NM_DHCP_CLIENT_GET_PRIVATE (self)->uuid;
}
/********************************************/
static void
timeout_cleanup (NMDHCPClient *self)
{
NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
if (priv->timeout_id) {
g_source_remove (priv->timeout_id);
priv->timeout_id = 0;
}
}
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;
}
}
void
nm_dhcp_client_stop_pid (GPid pid, const char *iface)
{
int i = 5; /* roughly 0.5 seconds */
g_return_if_fail (pid > 0);
/* Tell it to quit; maybe it wants to send out a RELEASE message */
kill (pid, SIGTERM);
while (i-- > 0) {
gint child_status;
int ret;
ret = waitpid (pid, &child_status, WNOHANG);
if (ret > 0)
break;
if (ret == -1) {
/* Child already exited */
if (errno == ECHILD) {
/* Was it really our child and it exited? */
if (kill (pid, 0) < 0 && errno == ESRCH)
break;
} else {
/* Took too long; shoot it in the head */
i = 0;
break;
}
}
g_usleep (G_USEC_PER_SEC / 10);
}
if (i <= 0) {
if (iface) {
nm_log_warn (LOGD_DHCP, "(%s): DHCP client pid %d didn't exit, will kill it.",
iface, pid);
}
kill (pid, SIGKILL);
nm_log_dbg (LOGD_DHCP, "waiting for DHCP client pid %d to exit", pid);
waitpid (pid, NULL, 0);
nm_log_dbg (LOGD_DHCP, "DHCP client pid %d cleaned up", pid);
}
}
static void
stop (NMDHCPClient *self, gboolean release, const GByteArray *duid)
{
NMDHCPClientPrivate *priv;
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);
priv->info_only = FALSE;
}
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 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;
}
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);
}
}
}
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
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_ip4 (NMDHCPClient *self,
NMSettingIP4Config *s_ip4,
guint8 *dhcp_anycast_addr,
const char *hostname)
{
NMDHCPClientPrivate *priv;
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 == FALSE, FALSE);
g_return_val_if_fail (priv->uuid != NULL, FALSE);
nm_log_info (LOGD_DHCP, "Activation (%s) Beginning DHCPv4 transaction (timeout in %d seconds)",
priv->iface, priv->timeout);
priv->pid = NM_DHCP_CLIENT_GET_CLASS (self)->ip4_start (self, s_ip4, dhcp_anycast_addr, hostname);
if (priv->pid)
start_monitor (self);
return priv->pid ? TRUE : FALSE;
}
/* uuid_parse does not work for machine-id, so we use our own converter */
static gboolean
machine_id_parse (const char *in, uuid_t uu)
{
const char *cp;
int i;
char buf[3];
g_return_val_if_fail (in != NULL, FALSE);
g_return_val_if_fail (strlen (in) == 32, FALSE);
for (i = 0; i < 32; i++) {
if (!g_ascii_isxdigit (in[i]))
return FALSE;
}
buf[2] = 0;
cp = in;
for (i = 0; i < 16; i++) {
buf[0] = *cp++;
buf[1] = *cp++;
uu[i] = ((unsigned char) strtoul (buf, NULL, 16)) & 0xFF;
}
return TRUE;
}
static GByteArray *
generate_duid_from_machine_id (void)
{
GByteArray *duid;
char *contents = NULL;
GChecksum *sum;
guint8 buffer[32]; /* SHA256 digest size */
gsize sumlen = sizeof (buffer);
const guint16 duid_type = g_htons (4);
uuid_t uuid;
GRand *generator;
guint i;
gboolean success = FALSE;
/* Get the machine ID from /etc/machine-id; it's always in /etc no matter
* where our configured SYSCONFDIR is. Alternatively, it might be in
* LOCALSTATEDIR /lib/dbus/machine-id.
*/
if ( g_file_get_contents ("/etc/machine-id", &contents, NULL, NULL)
|| g_file_get_contents (LOCALSTATEDIR "/lib/dbus/machine-id", &contents, NULL, NULL)) {
contents = g_strstrip (contents);
success = machine_id_parse (contents, uuid);
if (success) {
/* Hash the machine ID so it's not leaked to the network */
sum = g_checksum_new (G_CHECKSUM_SHA256);
g_checksum_update (sum, (const guchar *) &uuid, sizeof (uuid));
g_checksum_get_digest (sum, buffer, &sumlen);
g_checksum_free (sum);
}
g_free (contents);
}
if (!success) {
nm_log_warn (LOGD_DHCP6, "Failed to read " SYSCONFDIR "/machine-id "
"or " LOCALSTATEDIR "/lib/dbus/machine-id to generate "
"DHCPv6 DUID; creating non-persistent random DUID.");
generator = g_rand_new ();
for (i = 0; i < sizeof (buffer) / sizeof (guint32); i++)
((guint32 *) buffer)[i] = g_rand_int (generator);
g_rand_free (generator);
}
/* Generate a DHCP Unique Identifier for DHCPv6 using the
* DUID-UUID method (see RFC 6355 section 4). Format is:
*
* u16: type (DUID-UUID = 4)
* u8[16]: UUID bytes
*/
duid = g_byte_array_sized_new (18);
g_byte_array_append (duid, (guint8 *) &duid_type, sizeof (duid_type));
/* Since SHA256 is 256 bits, but UUID is 128 bits, we just take the first
* 128 bits of the SHA256 as the DUID-UUID.
*/
g_byte_array_append (duid, buffer, 16);
return duid;
}
static char *
escape_duid (const GByteArray *duid)
{
guint32 i = 0;
GString *s;
g_return_val_if_fail (duid != NULL, NULL);
s = g_string_sized_new (40);
while (i < duid->len) {
if (s->len)
g_string_append_c (s, ':');
g_string_append_printf (s, "%02x", duid->data[i++]);
}
return g_string_free (s, FALSE);
}
static GByteArray *
get_duid (NMDHCPClient *self)
{
static GByteArray *duid = NULL;
GByteArray *copy = NULL;
char *escaped;
if (G_UNLIKELY (duid == NULL)) {
duid = generate_duid_from_machine_id ();
g_assert (duid);
if (nm_logging_enabled (LOGL_DEBUG, LOGD_DHCP6)) {
escaped = escape_duid (duid);
nm_log_dbg (LOGD_DHCP6, "Generated DUID %s", escaped);
g_free (escaped);
}
}
if (G_LIKELY (duid)) {
copy = g_byte_array_sized_new (duid->len);
g_byte_array_append (copy, duid->data, duid->len);
}
return copy;
}
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 (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_enabled (LOGL_DEBUG, LOGD_DHCP)) {
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;
}
void
nm_dhcp_client_stop_existing (const char *pid_file, const char *binary_name)
{
char *pid_contents = NULL, *proc_contents = NULL, *proc_path = NULL;
long int tmp;
/* Check for an existing instance and stop it */
if (!g_file_get_contents (pid_file, &pid_contents, NULL, NULL))
return;
errno = 0;
tmp = strtol (pid_contents, NULL, 10);
if ((errno == 0) && (tmp > 1)) {
const char *exe;
/* Ensure the process is a DHCP client */
proc_path = g_strdup_printf ("/proc/%ld/cmdline", tmp);
if (g_file_get_contents (proc_path, &proc_contents, NULL, NULL)) {
exe = strrchr (proc_contents, '/');
if (exe)
exe++;
else
exe = proc_contents;
if (!strcmp (exe, binary_name))
nm_dhcp_client_stop_pid ((GPid) tmp, NULL);
}
}
if (remove (pid_file) == -1)
nm_log_dbg (LOGD_DHCP, "Could not remove dhcp pid file \"%s\": %d (%s)", pid_file, errno, g_strerror (errno));
g_free (proc_path);
g_free (pid_contents);
g_free (proc_contents);
}
void
nm_dhcp_client_stop (NMDHCPClient *self, gboolean release)
{
NMDHCPClientPrivate *priv;
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->duid);
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 gboolean
state_is_bound (guint32 state)
{
if ( (state == DHC_BOUND4)
|| (state == DHC_BOUND6)
|| (state == DHC_RENEW4)
|| (state == DHC_RENEW6)
|| (state == DHC_REBOOT)
|| (state == DHC_REBIND4)
|| (state == DHC_REBIND6)
|| (state == DHC_IPV4LL))
return TRUE;
return FALSE;
}
typedef struct {
NMDHCPState state;
const char *name;
} DhcState;
#define STATE_TABLE_SIZE (sizeof (state_table) / sizeof (state_table[0]))
static DhcState state_table[] = {
{ DHC_NBI, "nbi" },
{ DHC_PREINIT, "preinit" },
{ DHC_PREINIT6,"preinit6" },
{ DHC_BOUND4, "bound" },
{ DHC_BOUND6, "bound6" },
{ DHC_IPV4LL, "ipv4ll" },
{ DHC_RENEW4, "renew" },
{ DHC_RENEW6, "renew6" },
{ DHC_REBOOT, "reboot" },
{ DHC_REBIND4, "rebind" },
{ DHC_REBIND6, "rebind6" },
{ DHC_STOP, "stop" },
{ DHC_STOP6, "stop6" },
{ DHC_MEDIUM, "medium" },
{ DHC_TIMEOUT, "timeout" },
{ DHC_FAIL, "fail" },
{ DHC_EXPIRE, "expire" },
{ DHC_EXPIRE6, "expire6" },
{ DHC_RELEASE, "release" },
{ DHC_RELEASE6,"release6" },
{ DHC_START, "start" },
{ DHC_ABEND, "abend" },
{ DHC_END, "end" },
{ DHC_DEPREF6, "depref6" },
};
static inline const char *
state_to_string (guint32 state)
{
int i;
for (i = 0; i < STATE_TABLE_SIZE; i++) {
if (state == state_table[i].state)
return state_table[i].name;
}
return NULL;
}
static inline NMDHCPState
string_to_state (const char *name)
{
int i;
for (i = 0; i < STATE_TABLE_SIZE; i++) {
if (!strcasecmp (name, state_table[i].name))
return state_table[i].state;
}
return 255;
}
static char *
garray_to_string (GArray *array, const char *key)
{
GString *str;
int i;
unsigned char c;
char *converted = NULL;
g_return_val_if_fail (array != NULL, NULL);
/* Since the DHCP options come through environment variables, they should
* already be UTF-8 safe, but just make sure.
*/
str = g_string_sized_new (array->len);
for (i = 0; i < array->len; i++) {
c = array->data[i];
/* Convert NULLs to spaces and non-ASCII characters to ? */
if (c == '\0')
c = ' ';
else if (c > 127)
c = '?';
str = g_string_append_c (str, c);
}
str = g_string_append_c (str, '\0');
converted = str->str;
if (!g_utf8_validate (converted, -1, NULL))
nm_log_warn (LOGD_DHCP, "DHCP option '%s' couldn't be converted to UTF-8", key);
g_string_free (str, FALSE);
return converted;
}
static void
copy_option (gpointer key,
gpointer value,
gpointer user_data)
{
GHashTable *hash = user_data;
const char *str_key = (const char *) key;
char *str_value = NULL;
if (G_VALUE_TYPE (value) != DBUS_TYPE_G_UCHAR_ARRAY) {
nm_log_warn (LOGD_DHCP, "unexpected key %s value type was not "
"DBUS_TYPE_G_UCHAR_ARRAY",
str_key);
return;
}
str_value = garray_to_string ((GArray *) g_value_get_boxed (value), str_key);
if (str_value)
g_hash_table_insert (hash, g_strdup (str_key), str_value);
}
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 (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);
}
#define NEW_TAG "new_"
#define OLD_TAG "old_"
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 (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
ip4_process_dhcpcd_rfc3442_routes (const char *str,
NMIP4Config *ip4_config,
guint32 *gwaddr)
{
char **routes, **r;
gboolean have_routes = FALSE;
routes = g_strsplit (str, " ", 0);
if (g_strv_length (routes) == 0)
goto out;
if ((g_strv_length (routes) % 2) != 0) {
nm_log_warn (LOGD_DHCP4, " classless static routes provided, but invalid");
goto out;
}
for (r = routes; *r; r += 2) {
char *slash;
NMPlatformIP4Route route;
int rt_cidr = 32;
guint32 rt_addr, rt_route;
slash = strchr(*r, '/');
if (slash) {
*slash = '\0';
errno = 0;
rt_cidr = strtol (slash + 1, NULL, 10);
if ((errno == EINVAL) || (errno == ERANGE)) {
nm_log_warn (LOGD_DHCP4, "DHCP provided invalid classless static route cidr: '%s'", slash + 1);
continue;
}
}
if (inet_pton (AF_INET, *r, &rt_addr) <= 0) {
nm_log_warn (LOGD_DHCP4, "DHCP provided invalid classless static route address: '%s'", *r);
continue;
}
if (inet_pton (AF_INET, *(r + 1), &rt_route) <= 0) {
nm_log_warn (LOGD_DHCP4, "DHCP provided invalid classless static route gateway: '%s'", *(r + 1));
continue;
}
have_routes = TRUE;
if (rt_cidr == 0 && rt_addr == 0) {
/* FIXME: how to handle multiple routers? */
*gwaddr = rt_route;
} else {
nm_log_info (LOGD_DHCP4, " classless static route %s/%d gw %s", *r, rt_cidr, *(r + 1));
memset (&route, 0, sizeof (route));
route.network = rt_addr;
route.plen = rt_cidr;
route.gateway = rt_route;
route.source = NM_PLATFORM_SOURCE_DHCP;
nm_ip4_config_add_route (ip4_config, &route);
}
}
out:
g_strfreev (routes);
return have_routes;
}
static const char **
process_dhclient_rfc3442_route (const char **octets, NMPlatformIP4Route *route, gboolean *success)
{
const char **o = octets;
int addr_len = 0, i = 0;
long int tmp;
char *next_hop;
guint32 tmp_addr;
*success = FALSE;
if (!*o)
return o; /* no prefix */
tmp = strtol (*o, NULL, 10);
if (tmp < 0 || tmp > 32) /* 32 == max IP4 prefix length */
return o;
memset (route, 0, sizeof (*route));
route->plen = tmp;
o++;
if (tmp > 0)
addr_len = ((tmp - 1) / 8) + 1;
/* ensure there's at least the address + next hop left */
if (g_strv_length ((char **) o) < addr_len + 4)
goto error;
if (tmp) {
const char *addr[4] = { "0", "0", "0", "0" };
char *str_addr;
for (i = 0; i < addr_len; i++)
addr[i] = *o++;
str_addr = g_strjoin (".", addr[0], addr[1], addr[2], addr[3], NULL);
if (inet_pton (AF_INET, str_addr, &tmp_addr) <= 0) {
g_free (str_addr);
goto error;
}
tmp_addr &= nm_utils_ip4_prefix_to_netmask ((guint32) tmp);
route->network = tmp_addr;
}
/* Handle next hop */
next_hop = g_strjoin (".", o[0], o[1], o[2], o[3], NULL);
if (inet_pton (AF_INET, next_hop, &tmp_addr) <= 0) {
g_free (next_hop);
goto error;
}
route->gateway = tmp_addr;
g_free (next_hop);
*success = TRUE;
return o + 4; /* advance to past the next hop */
error:
return o;
}
static gboolean
ip4_process_dhclient_rfc3442_routes (const char *str,
NMIP4Config *ip4_config,
guint32 *gwaddr)
{
char **octets, **o;
gboolean have_routes = FALSE;
NMPlatformIP4Route route;
gboolean success;
o = octets = g_strsplit_set (str, " .", 0);
if (g_strv_length (octets) < 5) {
nm_log_warn (LOGD_DHCP4, "ignoring invalid classless static routes '%s'", str);
goto out;
}
while (*o) {
memset (&route, 0, sizeof (route));
o = (char **) process_dhclient_rfc3442_route ((const char **) o, &route, &success);
if (!success) {
nm_log_warn (LOGD_DHCP4, "ignoring invalid classless static routes");
break;
}
have_routes = TRUE;
if (!route.plen) {
/* gateway passed as classless static route */
*gwaddr = route.gateway;
} else {
char addr[INET_ADDRSTRLEN];
/* normal route */
route.source = NM_PLATFORM_SOURCE_DHCP;
nm_ip4_config_add_route (ip4_config, &route);
nm_log_info (LOGD_DHCP4, " classless static route %s/%d gw %s",
nm_utils_inet4_ntop (route.network, addr), route.plen,
nm_utils_inet4_ntop (route.gateway, NULL));
}
}
out:
g_strfreev (octets);
return have_routes;
}
static gboolean
ip4_process_classless_routes (GHashTable *options,
NMIP4Config *ip4_config,
guint32 *gwaddr)
{
const char *str, *p;
g_return_val_if_fail (options != NULL, FALSE);
g_return_val_if_fail (ip4_config != NULL, FALSE);
*gwaddr = 0;
/* dhcpd/dhclient in Fedora has support for rfc3442 implemented using a
* slightly different format:
*
* option classless-static-routes = array of (destination-descriptor ip-address);
*
* which results in:
*
* 0 192.168.0.113 25.129.210.177.132 192.168.0.113 7.2 10.34.255.6
*
* dhcpcd supports classless static routes natively and uses this same
* option identifier with the following format:
*
* 192.168.10.0/24 192.168.1.1 10.0.0.0/8 10.17.66.41
*/
str = g_hash_table_lookup (options, "new_classless_static_routes");
/* dhclient doesn't have actual support for rfc3442 classless static routes
* upstream. Thus, people resort to defining the option in dhclient.conf
* and using arbitrary formats like so:
*
* option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
*
* See https://lists.isc.org/pipermail/dhcp-users/2008-December/007629.html
*/
if (!str)
str = g_hash_table_lookup (options, "new_rfc3442_classless_static_routes");
/* Microsoft version; same as rfc3442 but with a different option # (249) */
if (!str)
str = g_hash_table_lookup (options, "new_ms_classless_static_routes");
if (!str || !strlen (str))
return FALSE;
p = str;
while (*p) {
if (!g_ascii_isdigit (*p) && (*p != ' ') && (*p != '.') && (*p != '/')) {
nm_log_warn (LOGD_DHCP4, "ignoring invalid classless static routes '%s'", str);
return FALSE;
}
p++;
};
if (strchr (str, '/')) {
/* dhcpcd format */
return ip4_process_dhcpcd_rfc3442_routes (str, ip4_config, gwaddr);
}
return ip4_process_dhclient_rfc3442_routes (str, ip4_config, gwaddr);
}
static void
process_classful_routes (GHashTable *options, NMIP4Config *ip4_config)
{
const char *str;
char **searches, **s;
str = g_hash_table_lookup (options, "new_static_routes");
if (!str)
return;
searches = g_strsplit (str, " ", 0);
if ((g_strv_length (searches) % 2)) {
nm_log_info (LOGD_DHCP, " static routes provided, but invalid");
goto out;
}
for (s = searches; *s; s += 2) {
NMPlatformIP4Route route;
guint32 rt_addr, rt_route;
if (inet_pton (AF_INET, *s, &rt_addr) <= 0) {
nm_log_warn (LOGD_DHCP, "DHCP provided invalid static route address: '%s'", *s);
continue;
}
if (inet_pton (AF_INET, *(s + 1), &rt_route) <= 0) {
nm_log_warn (LOGD_DHCP, "DHCP provided invalid static route gateway: '%s'", *(s + 1));
continue;
}
// FIXME: ensure the IP address and route are sane
memset (&route, 0, sizeof (route));
route.network = rt_addr;
/* RFC 2132, updated by RFC 3442:
The Static Routes option (option 33) does not provide a subnet mask
for each route - it is assumed that the subnet mask is implicit in
whatever network number is specified in each route entry */
route.plen = nm_utils_ip4_get_default_prefix (rt_addr);
if (rt_addr & ~nm_utils_ip4_prefix_to_netmask (route.plen)) {
/* RFC 943: target not "this network"; using host routing */
route.plen = 32;
}
route.gateway = rt_route;
route.source = NM_PLATFORM_SOURCE_DHCP;
nm_ip4_config_add_route (ip4_config, &route);
nm_log_info (LOGD_DHCP, " static route %s",
nm_platform_ip4_route_to_string (&route));
}
out:
g_strfreev (searches);
}
static void
process_domain_search (const char *str, GFunc add_func, gpointer user_data)
{
char **searches, **s;
char *unescaped, *p;
int i;
g_return_if_fail (str != NULL);
g_return_if_fail (add_func != NULL);
p = unescaped = g_strdup (str);
do {
p = strstr (p, "\\032");
if (!p)
break;
/* Clear the escaped space with real spaces */
for (i = 0; i < 4; i++)
*p++ = ' ';
} while (*p++);
if (strchr (unescaped, '\\')) {
nm_log_warn (LOGD_DHCP, " invalid domain search: '%s'", unescaped);
goto out;
}
searches = g_strsplit (unescaped, " ", 0);
for (s = searches; *s; s++) {
if (strlen (*s)) {
nm_log_info (LOGD_DHCP, " domain search '%s'", *s);
add_func (*s, user_data);
}
}
g_strfreev (searches);
out:
g_free (unescaped);
}
static void
ip4_add_domain_search (gpointer data, gpointer user_data)
{
nm_ip4_config_add_search (NM_IP4_CONFIG (user_data), (const char *) data);
}
/* 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;
guint32 tmp_addr;
NMPlatformIP4Address address;
char *str = NULL;
guint32 gwaddr = 0, plen = 0;
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 ();
memset (&address, 0, sizeof (address));
address.timestamp = nm_utils_get_monotonic_timestamp_s ();
str = g_hash_table_lookup (priv->options, "new_ip_address");
if (str && (inet_pton (AF_INET, str, &tmp_addr) > 0)) {
address.address = tmp_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)) {
plen = nm_utils_ip4_netmask_to_prefix (tmp_addr);
nm_log_info (LOGD_DHCP4, " plen %d (%s)", plen, str);
} else {
/* Get default netmask for the IP according to appropriate class. */
plen = nm_utils_ip4_get_default_prefix (address.address);
nm_log_info (LOGD_DHCP4, " plen %d (default)", plen);
}
address.plen = plen;
/* 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) {
nm_log_info (LOGD_DHCP4, " gateway %s", nm_utils_inet4_ntop (gwaddr, NULL));
nm_ip4_config_set_gateway (ip4_config, 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, &gwaddr) > 0) {
nm_ip4_config_set_gateway (ip4_config, gwaddr);
nm_log_info (LOGD_DHCP4, " gateway %s", *s);
break;
} else
nm_log_warn (LOGD_DHCP4, "ignoring invalid gateway '%s'", *s);
}
g_strfreev (routers);
}
}
/*
* RFC 2132, section 9.7
* DHCP clients use the contents of the 'server identifier' field
* as the destination address for any DHCP messages unicast to
* the DHCP server.
*
* Some ISP's provide leases from central servers that are on
* different subnets that the address offered. If the host
* does not configure the interface as the default route, the
* dhcp server may not be reachable via unicast, and a host
* specific route is needed.
**/
str = g_hash_table_lookup (priv->options, "new_dhcp_server_identifier");
if (str) {
if (inet_pton (AF_INET, str, &tmp_addr) > 0) {
NMPlatformIP4Route route;
guint32 mask = nm_utils_ip4_prefix_to_netmask (address.plen);
nm_log_info (LOGD_DHCP4, " server identifier %s", str);
if ((tmp_addr & mask) != (address.address & mask)) {
/* DHCP server not on assigned subnet, route needed */
memset (&route, 0, sizeof (route));
route.network = tmp_addr;
route.plen = 32;
/* this will be a device route if gwaddr is 0 */
route.gateway = gwaddr;
nm_ip4_config_add_route (ip4_config, &route);
nm_log_dbg (LOGD_IP, "adding route for server identifier: %s",
nm_platform_ip4_route_to_string (&route));
}
}
else
nm_log_warn (LOGD_DHCP4, "ignoring invalid server identifier '%s'", str);
}
str = g_hash_table_lookup (priv->options, "new_dhcp_lease_time");
if (str) {
address.lifetime = address.preferred = strtoul (str, NULL, 10);
nm_log_info (LOGD_DHCP4, " lease time %d", address.lifetime);
}
address.source = NM_PLATFORM_SOURCE_DHCP;
nm_ip4_config_add_address (ip4_config, &address);
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);
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);
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);
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:
g_object_unref (ip4_config);
return NULL;
}
NMIP4Config *
nm_dhcp_client_get_ip4_config (NMDHCPClient *self, gboolean test)
{
NMDHCPClientPrivate *priv;
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_DHCP4, "(%s): DHCPv4 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 ip4_options_to_config (self);
}
/********************************************/
static void
ip6_add_domain_search (gpointer data, gpointer user_data)
{
nm_ip6_config_add_search (NM_IP6_CONFIG (user_data), (const char *) data);
}
/* 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;
NMPlatformIP6Address address;
char *str = NULL;
GHashTableIter iter;
gpointer key, value;
g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
memset (&address, 0, sizeof (address));
address.plen = 128;
address.timestamp = nm_utils_get_monotonic_timestamp_s ();
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 ();
str = g_hash_table_lookup (priv->options, "new_dhcp_lease_time");
if (str) {
address.lifetime = address.preferred = strtoul (str, NULL, 10);
nm_log_info (LOGD_DHCP6, " lease time %d", address.lifetime);
}
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;
}
address.address = tmp_addr;
address.source = NM_PLATFORM_SOURCE_DHCP;
nm_ip6_config_add_address (ip6_config, &address);
nm_log_info (LOGD_DHCP6, " address %s", str);
} 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;
}
NMIP6Config *
nm_dhcp_client_get_ip6_config (NMDHCPClient *self, gboolean test)
{
NMDHCPClientPrivate *priv;
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
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;
}
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
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_HWADDR:
/* construct only */
priv->hwaddr = g_value_dup_boxed (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;
}
}
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);
priv->remove_id = 0;
}
if (priv->options) {
g_hash_table_destroy (priv->options);
priv->options = NULL;
}
g_clear_pointer (&priv->iface, g_free);
if (priv->hwaddr) {
g_byte_array_free (priv->hwaddr, TRUE);
priv->hwaddr = NULL;
}
if (priv->duid) {
g_byte_array_free (priv->duid, TRUE);
priv->duid = NULL;
}
G_OBJECT_CLASS (nm_dhcp_client_parent_class)->dispose (object);
}
static void
nm_dhcp_client_class_init (NMDHCPClientClass *client_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (client_class);
g_type_class_add_private (client_class, sizeof (NMDHCPClientPrivate));
/* virtual methods */
object_class->dispose = dispose;
object_class->get_property = get_property;
object_class->set_property = set_property;
client_class->stop = stop;
client_class->get_duid = get_duid;
g_object_class_install_property
(object_class, PROP_IFACE,
g_param_spec_string (NM_DHCP_CLIENT_INTERFACE,
"iface",
"Interface",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property
(object_class, PROP_HWADDR,
g_param_spec_boxed (NM_DHCP_CLIENT_HWADDR,
"hwaddr",
"hardware address",
G_TYPE_BYTE_ARRAY,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property
(object_class, PROP_IPV6,
g_param_spec_boolean (NM_DHCP_CLIENT_IPV6,
"ipv6",
"IPv6",
FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property
(object_class, PROP_UUID,
g_param_spec_string (NM_DHCP_CLIENT_UUID,
"uuid",
"UUID",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property
(object_class, PROP_TIMEOUT,
g_param_spec_uint (NM_DHCP_CLIENT_TIMEOUT,
"timeout",
"Timeout",
0, G_MAXUINT, 45,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
/* signals */
signals[STATE_CHANGED] =
g_signal_new ("state-changed",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMDHCPClientClass, state_changed),
NULL, NULL,
g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE, 1, G_TYPE_UINT);
signals[TIMEOUT] =
g_signal_new ("timeout",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMDHCPClientClass, timeout),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[REMOVE] =
g_signal_new ("remove",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMDHCPClientClass, remove),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}