
NMDhcpState is very tied to events from dhclient. But most of these states we don't care about, and NMDhcpClient definitely should abstract and hide them. We should repurpose NMDhcpState to simpler state. For that, first drop the state from nm_dhcp_client_handle_event(). This is only the first step (which arguably makes the code more complicated, because reason_to_state() gets spread out and the logic happens more than once). That will be addressed next.
1239 lines
40 KiB
C
1239 lines
40 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/*
|
|
* Copyright (C) 2005 - 2010 Red Hat, Inc.
|
|
*/
|
|
|
|
#include "src/core/nm-default-daemon.h"
|
|
|
|
#include "nm-dhcp-client.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <linux/if_ether.h>
|
|
|
|
#include "libnm-glib-aux/nm-dedup-multi.h"
|
|
#include "libnm-glib-aux/nm-random-utils.h"
|
|
|
|
#include "NetworkManagerUtils.h"
|
|
#include "nm-utils.h"
|
|
#include "nm-l3cfg.h"
|
|
#include "nm-l3-config-data.h"
|
|
#include "nm-dhcp-utils.h"
|
|
#include "nm-dhcp-options.h"
|
|
#include "libnm-platform/nm-platform.h"
|
|
#include "nm-hostname-manager.h"
|
|
#include "libnm-systemd-shared/nm-sd-utils-shared.h"
|
|
|
|
#include "nm-dhcp-client-logging.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
enum {
|
|
SIGNAL_NOTIFY,
|
|
LAST_SIGNAL,
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = {0};
|
|
|
|
NM_GOBJECT_PROPERTIES_DEFINE(NMDhcpClient, PROP_CONFIG, );
|
|
|
|
typedef struct _NMDhcpClientPrivate {
|
|
NMDhcpClientConfig config;
|
|
const NML3ConfigData *l3cd;
|
|
GSource *no_lease_timeout_source;
|
|
GSource *ipv6_lladdr_timeout_source;
|
|
GSource *watch_source;
|
|
GBytes *effective_client_id;
|
|
pid_t pid;
|
|
bool iaid_explicit : 1;
|
|
bool is_stopped : 1;
|
|
struct {
|
|
gulong id;
|
|
bool wait_dhcp_commit : 1;
|
|
bool wait_ll_address : 1;
|
|
} l3cfg_notify;
|
|
} NMDhcpClientPrivate;
|
|
|
|
G_DEFINE_ABSTRACT_TYPE(NMDhcpClient, nm_dhcp_client, G_TYPE_OBJECT)
|
|
|
|
#define NM_DHCP_CLIENT_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMDhcpClient, NM_IS_DHCP_CLIENT)
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcpClient *self);
|
|
|
|
/*****************************************************************************/
|
|
|
|
/* we use pid=-1 for invalid PIDs. Ensure that pid_t can hold negative values. */
|
|
G_STATIC_ASSERT(!(((pid_t) -1) > 0));
|
|
|
|
/*****************************************************************************/
|
|
|
|
NM_UTILS_LOOKUP_STR_DEFINE(nm_dhcp_state_to_string,
|
|
NMDhcpState,
|
|
NM_UTILS_LOOKUP_DEFAULT(NULL),
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_STATE_BOUND, "bound"),
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_STATE_DONE, "done"),
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_STATE_EXPIRE, "expire"),
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_STATE_EXTENDED, "extended"),
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_STATE_FAIL, "fail"),
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_STATE_NOOP, "noop"),
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_STATE_TERMINATED, "terminated"),
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_STATE_TIMEOUT, "timeout"),
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_STATE_UNKNOWN, "unknown"), );
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
_emit_notify(NMDhcpClient *self, const NMDhcpClientNotifyData *notify_data)
|
|
{
|
|
g_signal_emit(G_OBJECT(self), signals[SIGNAL_NOTIFY], 0, notify_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
connect_l3cfg_notify(NMDhcpClient *self)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
gboolean do_connect;
|
|
|
|
do_connect = priv->l3cfg_notify.wait_dhcp_commit | priv->l3cfg_notify.wait_ll_address;
|
|
|
|
if (!do_connect) {
|
|
nm_clear_g_signal_handler(priv->config.l3cfg, &priv->l3cfg_notify.id);
|
|
return;
|
|
}
|
|
|
|
if (priv->l3cfg_notify.id == 0) {
|
|
priv->l3cfg_notify.id = g_signal_connect(priv->config.l3cfg,
|
|
NM_L3CFG_SIGNAL_NOTIFY,
|
|
G_CALLBACK(l3_cfg_notify_cb),
|
|
self);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
nm_dhcp_client_set_effective_client_id(NMDhcpClient *self, GBytes *client_id)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
gs_free char *tmp_str = NULL;
|
|
|
|
g_return_if_fail(NM_IS_DHCP_CLIENT(self));
|
|
g_return_if_fail(!client_id || g_bytes_get_size(client_id) >= 2);
|
|
|
|
priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
if (nm_g_bytes_equal0(priv->effective_client_id, client_id))
|
|
return;
|
|
|
|
g_bytes_unref(priv->effective_client_id);
|
|
priv->effective_client_id = nm_g_bytes_ref(client_id);
|
|
|
|
_LOGT("%s: set %s",
|
|
priv->config.addr_family == AF_INET6 ? "duid" : "client-id",
|
|
priv->effective_client_id
|
|
? (tmp_str = nm_dhcp_utils_duid_to_string(priv->effective_client_id))
|
|
: "default");
|
|
}
|
|
|
|
const NMDhcpClientConfig *
|
|
nm_dhcp_client_get_config(NMDhcpClient *self)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
return &priv->config;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
pid_t
|
|
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;
|
|
}
|
|
|
|
static void
|
|
watch_cleanup(NMDhcpClient *self)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
nm_clear_g_source_inst(&priv->watch_source);
|
|
}
|
|
|
|
void
|
|
nm_dhcp_client_stop_pid(pid_t pid, const char *iface)
|
|
{
|
|
char *name = iface ? g_strdup_printf("dhcp-client-%s", iface) : NULL;
|
|
|
|
g_return_if_fail(pid > 1);
|
|
|
|
nm_utils_kill_child_sync(pid,
|
|
SIGTERM,
|
|
LOGD_DHCP,
|
|
name ?: "dhcp-client",
|
|
NULL,
|
|
1000 / 2,
|
|
1000 / 20);
|
|
g_free(name);
|
|
}
|
|
|
|
static void
|
|
stop(NMDhcpClient *self, gboolean release)
|
|
{
|
|
NMDhcpClientPrivate *priv;
|
|
|
|
g_return_if_fail(NM_IS_DHCP_CLIENT(self));
|
|
|
|
priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
if (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->config.iface);
|
|
}
|
|
priv->pid = -1;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gboolean
|
|
_no_lease_timeout(gpointer user_data)
|
|
{
|
|
NMDhcpClient *self = user_data;
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
nm_clear_g_source_inst(&priv->no_lease_timeout_source);
|
|
|
|
_emit_notify(self,
|
|
&((NMDhcpClientNotifyData){
|
|
.notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_NO_LEASE_TIMEOUT,
|
|
}));
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
_no_lease_timeout_schedule(NMDhcpClient *self)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
if (priv->no_lease_timeout_source)
|
|
return;
|
|
|
|
if (priv->config.timeout == NM_DHCP_TIMEOUT_INFINITY) {
|
|
_LOGI("activation: beginning transaction (no timeout)");
|
|
priv->no_lease_timeout_source = g_source_ref(nm_g_source_sentinel_get(0));
|
|
} else {
|
|
_LOGI("activation: beginning transaction (timeout in %u seconds)",
|
|
(guint) priv->config.timeout);
|
|
priv->no_lease_timeout_source =
|
|
nm_g_timeout_add_seconds_source(priv->config.timeout, _no_lease_timeout, self);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
nm_dhcp_client_set_state(NMDhcpClient *self, NMDhcpState new_state, const NML3ConfigData *l3cd)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
GHashTable *options;
|
|
const int IS_IPv4 = NM_IS_IPv4(priv->config.addr_family);
|
|
nm_auto_unref_l3cd const NML3ConfigData *l3cd_merged = NULL;
|
|
|
|
if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED)) {
|
|
nm_assert(NM_IS_L3_CONFIG_DATA(l3cd));
|
|
nm_assert(nm_l3_config_data_get_dhcp_lease(l3cd, priv->config.addr_family));
|
|
} else
|
|
nm_assert(!l3cd);
|
|
|
|
if (l3cd)
|
|
nm_l3_config_data_seal(l3cd);
|
|
|
|
if (new_state >= NM_DHCP_STATE_TIMEOUT)
|
|
watch_cleanup(self);
|
|
|
|
if (!IS_IPv4 && l3cd) {
|
|
/* nm_dhcp_utils_merge_new_dhcp6_lease() relies on "life_starts" option
|
|
* for merging, which is only set by dhclient. Internal client never sets that,
|
|
* but it supports multiple IP addresses per lease. */
|
|
if (nm_dhcp_utils_merge_new_dhcp6_lease(priv->l3cd, l3cd, &l3cd_merged)) {
|
|
_LOGD("lease merged with existing one");
|
|
l3cd = nm_l3_config_data_seal(l3cd_merged);
|
|
}
|
|
}
|
|
|
|
if (priv->l3cd == l3cd)
|
|
return;
|
|
|
|
if (l3cd) {
|
|
nm_clear_g_source_inst(&priv->no_lease_timeout_source);
|
|
} else {
|
|
if (priv->l3cd)
|
|
_no_lease_timeout_schedule(self);
|
|
}
|
|
|
|
/* FIXME(l3cfg:dhcp): the API of NMDhcpClient is changing to expose a simpler API.
|
|
* The internals like NMDhcpState should not be exposed (or possibly dropped in large
|
|
* parts). */
|
|
|
|
nm_l3_config_data_reset(&priv->l3cd, l3cd);
|
|
|
|
options = l3cd ? nm_dhcp_lease_get_options(
|
|
nm_l3_config_data_get_dhcp_lease(l3cd, priv->config.addr_family))
|
|
: NULL;
|
|
|
|
if (_LOGD_ENABLED()) {
|
|
if (options) {
|
|
gs_free const char **keys = NULL;
|
|
guint nkeys;
|
|
guint i;
|
|
|
|
keys = nm_strdict_get_keys(options, TRUE, &nkeys);
|
|
for (i = 0; i < nkeys; i++) {
|
|
_LOGD("option %-20s => '%s'",
|
|
keys[i],
|
|
(char *) g_hash_table_lookup(options, keys[i]));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_LOGI_ENABLED()) {
|
|
const char *req_str =
|
|
IS_IPv4 ? nm_dhcp_option_request_string(AF_INET, NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS)
|
|
: nm_dhcp_option_request_string(AF_INET6, NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS);
|
|
const char *addr = nm_g_hash_table_lookup(options, req_str);
|
|
|
|
_LOGI("state changed %s%s%s%s",
|
|
priv->l3cd ? "new lease" : "no lease",
|
|
NM_PRINT_FMT_QUOTED(addr, ", address=", addr, "", ""));
|
|
}
|
|
|
|
/* FIXME(l3cfg:dhcp:acd): NMDhcpClient must also do ACD. It needs acd_timeout_msec
|
|
* as a configuration parameter (in NMDhcpClientConfig). When ACD is enabled,
|
|
* when a new lease gets announced, it must first use NML3Cfg to run ACD on the
|
|
* interface (the previous lease -- if any -- will still be used at that point).
|
|
* If ACD fails, we call nm_dhcp_client_decline() and try to get a different
|
|
* lease.
|
|
* If ACD passes, we need to notify the new lease, and the user (NMDevice) may
|
|
* then configure the address. We need to watch the configured addresses (in NML3Cfg),
|
|
* and if the address appears there, we need to accept the lease. That is complicated
|
|
* but necessary, because we can only accept the lease after we configured the
|
|
* address.
|
|
*
|
|
* As a whole, ACD is transparent for the user (NMDevice). It's entirely managed
|
|
* by NMDhcpClient. Note that we do ACD through NML3Cfg, which centralizes IP handling
|
|
* for one interface, so for example if the same address happens to be configured
|
|
* as a static address (bypassing ACD), then NML3Cfg is aware of that and signals
|
|
* immediate success. */
|
|
|
|
if (nm_dhcp_client_can_accept(self) && new_state == NM_DHCP_STATE_BOUND && priv->l3cd
|
|
&& nm_l3_config_data_get_num_addresses(priv->l3cd, priv->config.addr_family) > 0) {
|
|
priv->l3cfg_notify.wait_dhcp_commit = TRUE;
|
|
} else {
|
|
priv->l3cfg_notify.wait_dhcp_commit = FALSE;
|
|
}
|
|
connect_l3cfg_notify(self);
|
|
|
|
{
|
|
const NMDhcpClientNotifyData notify_data = {
|
|
.notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE,
|
|
.lease_update =
|
|
{
|
|
.l3cd = priv->l3cd,
|
|
.accepted = !priv->l3cfg_notify.wait_dhcp_commit,
|
|
},
|
|
};
|
|
|
|
_emit_notify(self, ¬ify_data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
daemon_watch_cb(GPid pid, int status, gpointer user_data)
|
|
{
|
|
NMDhcpClient *self = NM_DHCP_CLIENT(user_data);
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
gs_free char *desc = NULL;
|
|
|
|
g_return_if_fail(priv->watch_source);
|
|
|
|
priv->watch_source = NULL;
|
|
|
|
_LOGI("client pid %d %s", pid, (desc = nm_utils_get_process_exit_status_desc(status)));
|
|
|
|
priv->pid = -1;
|
|
|
|
nm_dhcp_client_set_state(self, NM_DHCP_STATE_TERMINATED, NULL);
|
|
}
|
|
|
|
void
|
|
nm_dhcp_client_watch_child(NMDhcpClient *self, pid_t pid)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
g_return_if_fail(priv->pid == -1);
|
|
priv->pid = pid;
|
|
|
|
g_return_if_fail(!priv->watch_source);
|
|
priv->watch_source = nm_g_child_watch_add_source(pid, daemon_watch_cb, self);
|
|
}
|
|
|
|
void
|
|
nm_dhcp_client_stop_watch_child(NMDhcpClient *self, pid_t pid)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
g_return_if_fail(priv->pid == pid);
|
|
priv->pid = -1;
|
|
|
|
watch_cleanup(self);
|
|
}
|
|
|
|
gboolean
|
|
nm_dhcp_client_start_ip4(NMDhcpClient *self, GError **error)
|
|
{
|
|
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->config.addr_family == AF_INET, FALSE);
|
|
g_return_val_if_fail(priv->config.uuid, FALSE);
|
|
|
|
_no_lease_timeout_schedule(self);
|
|
|
|
return NM_DHCP_CLIENT_GET_CLASS(self)->ip4_start(self, error);
|
|
}
|
|
|
|
gboolean
|
|
nm_dhcp_client_accept(NMDhcpClient *self, GError **error)
|
|
{
|
|
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->l3cd, FALSE);
|
|
|
|
if (NM_DHCP_CLIENT_GET_CLASS(self)->accept) {
|
|
return NM_DHCP_CLIENT_GET_CLASS(self)->accept(self, error);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
nm_dhcp_client_can_accept(NMDhcpClient *self)
|
|
{
|
|
gboolean can_accept;
|
|
|
|
g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE);
|
|
|
|
can_accept = !!(NM_DHCP_CLIENT_GET_CLASS(self)->accept);
|
|
|
|
nm_assert(can_accept == (!!(NM_DHCP_CLIENT_GET_CLASS(self)->decline)));
|
|
|
|
return can_accept;
|
|
}
|
|
|
|
gboolean
|
|
nm_dhcp_client_decline(NMDhcpClient *self, const char *error_message, GError **error)
|
|
{
|
|
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->l3cd, FALSE);
|
|
|
|
if (NM_DHCP_CLIENT_GET_CLASS(self)->decline) {
|
|
return NM_DHCP_CLIENT_GET_CLASS(self)->decline(self, error_message, error);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GBytes *
|
|
get_duid(NMDhcpClient *self)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
ipv6_lladdr_timeout(gpointer user_data)
|
|
{
|
|
NMDhcpClient *self = user_data;
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
nm_clear_g_source_inst(&priv->ipv6_lladdr_timeout_source);
|
|
|
|
_emit_notify(
|
|
self,
|
|
&((NMDhcpClientNotifyData){
|
|
.notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD,
|
|
.it_looks_bad.reason = "timeout reached while waiting for an IPv6 link-local address",
|
|
}));
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static const NMPlatformIP6Address *
|
|
ipv6_lladdr_find(NMDhcpClient *self)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
NML3Cfg *l3cfg;
|
|
NMPLookup lookup;
|
|
NMDedupMultiIter iter;
|
|
const NMPObject *obj;
|
|
|
|
l3cfg = priv->config.l3cfg;
|
|
nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, nm_l3cfg_get_ifindex(l3cfg));
|
|
|
|
nm_platform_iter_obj_for_each (&iter, nm_l3cfg_get_platform(l3cfg), &lookup, &obj) {
|
|
const NMPlatformIP6Address *pladdr = NMP_OBJECT_CAST_IP6_ADDRESS(obj);
|
|
|
|
if (!IN6_IS_ADDR_LINKLOCAL(&pladdr->address))
|
|
continue;
|
|
if (NM_FLAGS_HAS(pladdr->n_ifa_flags, IFA_F_TENTATIVE)
|
|
&& !NM_FLAGS_HAS(pladdr->n_ifa_flags, IFA_F_OPTIMISTIC))
|
|
continue;
|
|
return pladdr;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcpClient *self)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
nm_assert(l3cfg == priv->config.l3cfg);
|
|
|
|
switch (notify_data->notify_type) {
|
|
case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE:
|
|
{
|
|
const NMPlatformIP6Address *addr;
|
|
gs_free_error GError *error = NULL;
|
|
|
|
if (!priv->l3cfg_notify.wait_ll_address)
|
|
return;
|
|
|
|
addr = ipv6_lladdr_find(self);
|
|
if (addr) {
|
|
_LOGD("got IPv6LL address, starting transaction");
|
|
priv->l3cfg_notify.wait_ll_address = FALSE;
|
|
connect_l3cfg_notify(self);
|
|
nm_clear_g_source_inst(&priv->ipv6_lladdr_timeout_source);
|
|
|
|
_no_lease_timeout_schedule(self);
|
|
|
|
if (!NM_DHCP_CLIENT_GET_CLASS(self)->ip6_start(self, &addr->address, &error)) {
|
|
_emit_notify(self,
|
|
&((NMDhcpClientNotifyData){
|
|
.notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD,
|
|
.it_looks_bad.reason = error->message,
|
|
}));
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT:
|
|
{
|
|
const NML3ConfigData *committed_l3cd;
|
|
NMDedupMultiIter ipconf_iter;
|
|
const NMPlatformIPAddress *lease_address;
|
|
gs_free_error GError *error = NULL;
|
|
|
|
/* A new configuration was committed to the interface. If we previously
|
|
* got a lease, check whether we are waiting for the address to be
|
|
* configured. If the address was added, we can proceed accepting the
|
|
* lease and notifying NMDevice. */
|
|
|
|
if (!priv->l3cfg_notify.wait_dhcp_commit)
|
|
return;
|
|
|
|
nm_l3_config_data_iter_ip_address_for_each (&ipconf_iter,
|
|
priv->l3cd,
|
|
priv->config.addr_family,
|
|
&lease_address)
|
|
break;
|
|
nm_assert(lease_address);
|
|
committed_l3cd = nm_l3cfg_get_combined_l3cd(l3cfg, TRUE);
|
|
|
|
if (priv->config.addr_family == AF_INET) {
|
|
const NMPlatformIP4Address *address4 = (const NMPlatformIP4Address *) lease_address;
|
|
|
|
if (!nm_l3_config_data_lookup_address_4(committed_l3cd,
|
|
address4->address,
|
|
address4->plen,
|
|
address4->peer_address))
|
|
return;
|
|
} else {
|
|
const NMPlatformIP6Address *address6 = (const NMPlatformIP6Address *) lease_address;
|
|
|
|
if (!nm_l3_config_data_lookup_address_6(committed_l3cd, &address6->address))
|
|
return;
|
|
}
|
|
|
|
priv->l3cfg_notify.wait_dhcp_commit = FALSE;
|
|
connect_l3cfg_notify(self);
|
|
|
|
_LOGD("accept address");
|
|
|
|
if (!nm_dhcp_client_accept(self, &error)) {
|
|
gs_free char *reason = g_strdup_printf("error accepting lease: %s", error->message);
|
|
|
|
_emit_notify(self,
|
|
&((NMDhcpClientNotifyData){
|
|
.notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD,
|
|
.it_looks_bad.reason = reason,
|
|
}));
|
|
return;
|
|
}
|
|
|
|
_emit_notify(
|
|
self,
|
|
&((NMDhcpClientNotifyData){.notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE,
|
|
.lease_update = {
|
|
.l3cd = priv->l3cd,
|
|
.accepted = TRUE,
|
|
}}));
|
|
break;
|
|
};
|
|
default:
|
|
/* ignore */;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
nm_dhcp_client_start_ip6(NMDhcpClient *self, GError **error)
|
|
{
|
|
NMDhcpClientPrivate *priv;
|
|
gs_unref_bytes GBytes *own_client_id = NULL;
|
|
const NMPlatformIP6Address *addr;
|
|
|
|
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->config.addr_family == AF_INET6, FALSE);
|
|
g_return_val_if_fail(priv->config.uuid, FALSE);
|
|
g_return_val_if_fail(!priv->effective_client_id, FALSE);
|
|
|
|
if (!priv->config.v6.enforce_duid)
|
|
own_client_id = NM_DHCP_CLIENT_GET_CLASS(self)->get_duid(self);
|
|
|
|
nm_dhcp_client_set_effective_client_id(self, own_client_id ?: priv->config.client_id);
|
|
|
|
addr = ipv6_lladdr_find(self);
|
|
if (!addr) {
|
|
_LOGD("waiting for IPv6LL address");
|
|
priv->l3cfg_notify.wait_ll_address = TRUE;
|
|
connect_l3cfg_notify(self);
|
|
priv->ipv6_lladdr_timeout_source =
|
|
nm_g_timeout_add_seconds_source(10, ipv6_lladdr_timeout, self);
|
|
return TRUE;
|
|
}
|
|
|
|
_no_lease_timeout_schedule(self);
|
|
|
|
return NM_DHCP_CLIENT_GET_CLASS(self)->ip6_start(self, &addr->address, error);
|
|
}
|
|
|
|
void
|
|
nm_dhcp_client_stop_existing(const char *pid_file, const char *binary_name)
|
|
{
|
|
guint64 start_time;
|
|
pid_t pid, ppid;
|
|
const char *exe;
|
|
char proc_path[NM_STRLEN("/proc/%lu/cmdline") + 100];
|
|
gs_free char *pid_contents = NULL, *proc_contents = NULL;
|
|
|
|
/* Check for an existing instance and stop it */
|
|
if (!g_file_get_contents(pid_file, &pid_contents, NULL, NULL))
|
|
return;
|
|
|
|
pid = _nm_utils_ascii_str_to_int64(pid_contents, 10, 1, G_MAXINT64, 0);
|
|
if (pid <= 0)
|
|
goto out;
|
|
|
|
start_time = nm_utils_get_start_time_for_pid(pid, NULL, &ppid);
|
|
if (start_time == 0)
|
|
goto out;
|
|
|
|
nm_sprintf_buf(proc_path, "/proc/%lu/cmdline", (unsigned long) pid);
|
|
if (!g_file_get_contents(proc_path, &proc_contents, NULL, NULL))
|
|
goto out;
|
|
|
|
exe = strrchr(proc_contents, '/');
|
|
if (exe)
|
|
exe++;
|
|
else
|
|
exe = proc_contents;
|
|
if (!nm_streq0(exe, binary_name))
|
|
goto out;
|
|
|
|
if (ppid == getpid()) {
|
|
/* the process is our own child. */
|
|
nm_utils_kill_child_sync(pid, SIGTERM, LOGD_DHCP, "dhcp-client", NULL, 1000 / 2, 1000 / 20);
|
|
} else {
|
|
nm_utils_kill_process_sync(pid,
|
|
start_time,
|
|
SIGTERM,
|
|
LOGD_DHCP,
|
|
"dhcp-client",
|
|
1000 / 2,
|
|
1000 / 20,
|
|
2000);
|
|
}
|
|
|
|
out:
|
|
if (remove(pid_file) == -1) {
|
|
int errsv = errno;
|
|
|
|
nm_log_dbg(LOGD_DHCP,
|
|
"dhcp: could not remove pid file \"%s\": %s (%d)",
|
|
pid_file,
|
|
nm_strerror_native(errsv),
|
|
errsv);
|
|
}
|
|
}
|
|
|
|
void
|
|
nm_dhcp_client_stop(NMDhcpClient *self, gboolean release)
|
|
{
|
|
NMDhcpClientPrivate *priv;
|
|
pid_t old_pid = 0;
|
|
|
|
g_return_if_fail(NM_IS_DHCP_CLIENT(self));
|
|
|
|
priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
if (priv->is_stopped)
|
|
return;
|
|
|
|
priv->is_stopped = TRUE;
|
|
|
|
priv->l3cfg_notify.wait_dhcp_commit = FALSE;
|
|
priv->l3cfg_notify.wait_ll_address = FALSE;
|
|
connect_l3cfg_notify(self);
|
|
|
|
/* Kill the DHCP client */
|
|
old_pid = priv->pid;
|
|
NM_DHCP_CLIENT_GET_CLASS(self)->stop(self, release);
|
|
if (old_pid > 0)
|
|
_LOGI("canceled DHCP transaction, DHCP client pid %d", old_pid);
|
|
else
|
|
_LOGI("canceled DHCP transaction");
|
|
nm_assert(priv->pid == -1);
|
|
|
|
nm_dhcp_client_set_state(self, NM_DHCP_STATE_TERMINATED, NULL);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static char *
|
|
bytearray_variant_to_string(NMDhcpClient *self, GVariant *value, const char *key)
|
|
{
|
|
const guint8 *array;
|
|
char *str;
|
|
gsize length;
|
|
gsize i;
|
|
|
|
nm_assert(value);
|
|
|
|
array = g_variant_get_fixed_array(value, &length, 1);
|
|
|
|
/* Since the DHCP options come originally came as environment variables, they
|
|
* have not guaranteed encoding. Let's only accept ASCII here.
|
|
*/
|
|
str = g_malloc(length + 1);
|
|
for (i = 0; i < length; i++) {
|
|
guint8 c = array[i];
|
|
|
|
if (c == '\0')
|
|
str[i] = ' ';
|
|
else if (c > 127)
|
|
str[i] = '?';
|
|
else
|
|
str[i] = (char) c;
|
|
}
|
|
str[i] = '\0';
|
|
|
|
return str;
|
|
}
|
|
|
|
static int
|
|
label_is_unknown_xyz(const char *label)
|
|
{
|
|
if (!NM_STR_HAS_PREFIX(label, "unknown_"))
|
|
return -EINVAL;
|
|
|
|
label += NM_STRLEN("unknown_");
|
|
if (label[0] != '2' || !g_ascii_isdigit(label[1]) || !g_ascii_isdigit(label[2])
|
|
|| label[3] != '\0')
|
|
return -EINVAL;
|
|
|
|
return _nm_utils_ascii_str_to_int64(label, 10, 224, 254, -EINVAL);
|
|
}
|
|
|
|
#define OLD_TAG "old_"
|
|
#define NEW_TAG "new_"
|
|
|
|
static void
|
|
maybe_add_option(NMDhcpClient *self, GHashTable *hash, const char *key, GVariant *value)
|
|
{
|
|
char *str_value;
|
|
int priv_opt_num;
|
|
|
|
if (!g_variant_is_of_type(value, G_VARIANT_TYPE_BYTESTRING))
|
|
return;
|
|
|
|
if (NM_STR_HAS_PREFIX(key, OLD_TAG))
|
|
return;
|
|
|
|
/* Filter out stuff that's not actually new DHCP options */
|
|
if (NM_IN_STRSET(key, "interface", "pid", "reason", "dhcp_message_type"))
|
|
return;
|
|
|
|
if (NM_STR_HAS_PREFIX(key, NEW_TAG))
|
|
key += NM_STRLEN(NEW_TAG);
|
|
if (NM_STR_HAS_PREFIX(key, "private_") || !key[0])
|
|
return;
|
|
|
|
str_value = bytearray_variant_to_string(self, value, key);
|
|
if (!str_value)
|
|
return;
|
|
|
|
g_hash_table_insert(hash, g_strdup(key), str_value);
|
|
|
|
/* dhclient has no special labels for private dhcp options: it uses "unknown_xyz"
|
|
* labels for that. We need to identify those to alias them to our "private_xyz"
|
|
* format unused in the internal dchp plugins.
|
|
*/
|
|
if ((priv_opt_num = label_is_unknown_xyz(key)) > 0) {
|
|
gs_free guint8 *check_val = NULL;
|
|
char *hex_str = NULL;
|
|
gsize len;
|
|
|
|
/* dhclient passes values from dhcp private options in its own "string" format:
|
|
* if the raw values are printable as ascii strings, it will pass the string
|
|
* representation; if the values are not printable as an ascii string, it will
|
|
* pass a string displaying the hex values (hex string). Try to enforce passing
|
|
* always an hex string, converting string representation if needed.
|
|
*/
|
|
check_val = nm_utils_hexstr2bin_alloc(str_value, FALSE, TRUE, ":", 0, &len);
|
|
hex_str = nm_utils_bin2hexstr_full(check_val ?: (guint8 *) str_value,
|
|
check_val ? len : strlen(str_value),
|
|
':',
|
|
FALSE,
|
|
NULL);
|
|
g_hash_table_insert(hash, g_strdup_printf("private_%d", priv_opt_num), hex_str);
|
|
}
|
|
}
|
|
|
|
void
|
|
nm_dhcp_client_emit_ipv6_prefix_delegated(NMDhcpClient *self, const NMPlatformIP6Address *prefix)
|
|
{
|
|
const NMDhcpClientNotifyData notify_data = {
|
|
.notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_PREFIX_DELEGATED,
|
|
.prefix_delegated =
|
|
{
|
|
.prefix = prefix,
|
|
},
|
|
};
|
|
|
|
_emit_notify(self, ¬ify_data);
|
|
}
|
|
|
|
gboolean
|
|
nm_dhcp_client_handle_event(gpointer unused,
|
|
const char *iface,
|
|
int pid,
|
|
GVariant *options,
|
|
const char *reason,
|
|
NMDhcpClient *self)
|
|
{
|
|
NMDhcpClientPrivate *priv;
|
|
nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
|
|
NMDhcpState new_state;
|
|
NMPlatformIP6Address prefix = {
|
|
0,
|
|
};
|
|
gboolean reason_is_bound;
|
|
|
|
g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE);
|
|
g_return_val_if_fail(iface != NULL, FALSE);
|
|
g_return_val_if_fail(pid > 0, FALSE);
|
|
g_return_val_if_fail(g_variant_is_of_type(options, G_VARIANT_TYPE_VARDICT), FALSE);
|
|
g_return_val_if_fail(reason != NULL, FALSE);
|
|
|
|
priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
if (!nm_streq0(priv->config.iface, iface))
|
|
return FALSE;
|
|
if (priv->pid != pid)
|
|
return FALSE;
|
|
|
|
_LOGD("DHCP event (reason: '%s')", reason);
|
|
|
|
if (NM_IN_STRSET_ASCII_CASE(reason, "preinit"))
|
|
return TRUE;
|
|
|
|
reason_is_bound = NM_IN_STRSET_ASCII_CASE(reason,
|
|
"bound",
|
|
"bound6",
|
|
"static",
|
|
"renew",
|
|
"renew6",
|
|
"reboot",
|
|
"rebind",
|
|
"rebind6");
|
|
|
|
if (reason_is_bound) {
|
|
gs_unref_hashtable GHashTable *str_options = NULL;
|
|
GVariantIter iter;
|
|
const char *name;
|
|
GVariant *value;
|
|
|
|
/* Copy options */
|
|
str_options = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free);
|
|
g_variant_iter_init(&iter, options);
|
|
while (g_variant_iter_next(&iter, "{&sv}", &name, &value)) {
|
|
maybe_add_option(self, str_options, name, value);
|
|
g_variant_unref(value);
|
|
}
|
|
|
|
/* Create the IP config */
|
|
if (g_hash_table_size(str_options) > 0) {
|
|
if (priv->config.addr_family == AF_INET) {
|
|
l3cd = nm_dhcp_utils_ip4_config_from_options(
|
|
nm_l3cfg_get_multi_idx(priv->config.l3cfg),
|
|
nm_l3cfg_get_ifindex(priv->config.l3cfg),
|
|
priv->config.iface,
|
|
str_options);
|
|
} else {
|
|
prefix = nm_dhcp_utils_ip6_prefix_from_options(str_options);
|
|
l3cd = nm_dhcp_utils_ip6_config_from_options(
|
|
nm_l3cfg_get_multi_idx(priv->config.l3cfg),
|
|
nm_l3cfg_get_ifindex(priv->config.l3cfg),
|
|
priv->config.iface,
|
|
str_options,
|
|
priv->config.v6.info_only);
|
|
}
|
|
} else
|
|
g_warn_if_reached();
|
|
|
|
if (l3cd) {
|
|
nm_l3_config_data_set_dhcp_lease_from_options(l3cd,
|
|
priv->config.addr_family,
|
|
g_steal_pointer(&str_options));
|
|
}
|
|
}
|
|
|
|
if (!IN6_IS_ADDR_UNSPECIFIED(&prefix.address)) {
|
|
/* If we got an IPv6 prefix to delegate, we don't change the state
|
|
* of the DHCP client instance. Instead, we just signal the prefix
|
|
* to the device. */
|
|
nm_dhcp_client_emit_ipv6_prefix_delegated(self, &prefix);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Fail if no valid IP config was received */
|
|
if (reason_is_bound && !l3cd) {
|
|
_LOGW("client bound but IP config not received");
|
|
new_state = NM_DHCP_STATE_FAIL;
|
|
} else {
|
|
if (NM_IN_STRSET_ASCII_CASE(reason, "bound", "bound6", "static"))
|
|
new_state = NM_DHCP_STATE_BOUND;
|
|
else if (NM_IN_STRSET_ASCII_CASE(reason, "renew", "renew6", "reboot", "rebind", "rebind6"))
|
|
new_state = NM_DHCP_STATE_EXTENDED;
|
|
else if (NM_IN_STRSET_ASCII_CASE(reason, "timeout"))
|
|
new_state = NM_DHCP_STATE_TIMEOUT;
|
|
else if (NM_IN_STRSET_ASCII_CASE(reason, "nak", "expire", "expire6"))
|
|
new_state = NM_DHCP_STATE_EXPIRE;
|
|
else if (NM_IN_STRSET_ASCII_CASE(reason, "end", "stop", "stopped"))
|
|
new_state = NM_DHCP_STATE_DONE;
|
|
else if (NM_IN_STRSET_ASCII_CASE(reason, "fail", "abend"))
|
|
new_state = NM_DHCP_STATE_FAIL;
|
|
else if (NM_IN_STRSET_ASCII_CASE(reason, "preinit"))
|
|
new_state = NM_DHCP_STATE_NOOP;
|
|
else
|
|
new_state = NM_DHCP_STATE_UNKNOWN;
|
|
}
|
|
|
|
nm_dhcp_client_set_state(self, new_state, l3cd);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
nm_dhcp_client_server_id_is_rejected(NMDhcpClient *self, gconstpointer addr)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
in_addr_t addr4 = *(in_addr_t *) addr;
|
|
guint i;
|
|
|
|
/* IPv6 not implemented yet */
|
|
nm_assert(priv->config.addr_family == AF_INET);
|
|
|
|
if (!priv->config.reject_servers || !priv->config.reject_servers[0])
|
|
return FALSE;
|
|
|
|
for (i = 0; priv->config.reject_servers[i]; i++) {
|
|
in_addr_t r_addr;
|
|
in_addr_t mask;
|
|
int r_prefix;
|
|
|
|
if (!nm_utils_parse_inaddr_prefix_bin(AF_INET,
|
|
priv->config.reject_servers[i],
|
|
NULL,
|
|
&r_addr,
|
|
&r_prefix))
|
|
nm_assert_not_reached();
|
|
mask = _nm_utils_ip4_prefix_to_netmask(r_prefix < 0 ? 32 : r_prefix);
|
|
if ((addr4 & mask) == (r_addr & mask))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
config_init(NMDhcpClientConfig *config, const NMDhcpClientConfig *src)
|
|
{
|
|
*config = *src;
|
|
|
|
g_object_ref(config->l3cfg);
|
|
|
|
if (config->hwaddr)
|
|
g_bytes_ref(config->hwaddr);
|
|
if (config->bcast_hwaddr)
|
|
g_bytes_ref(config->bcast_hwaddr);
|
|
if (config->vendor_class_identifier)
|
|
g_bytes_ref(config->vendor_class_identifier);
|
|
if (config->client_id)
|
|
g_bytes_ref(config->client_id);
|
|
|
|
config->iface = g_strdup(config->iface);
|
|
config->uuid = g_strdup(config->uuid);
|
|
config->anycast_address = g_strdup(config->anycast_address);
|
|
config->hostname = g_strdup(config->hostname);
|
|
config->mud_url = g_strdup(config->mud_url);
|
|
|
|
config->reject_servers = (const char *const *) nm_strv_dup(config->reject_servers, -1, TRUE);
|
|
|
|
if (config->addr_family == AF_INET) {
|
|
config->v4.last_address = g_strdup(config->v4.last_address);
|
|
} else if (config->addr_family == AF_INET6) {
|
|
config->hwaddr = NULL;
|
|
config->bcast_hwaddr = NULL;
|
|
config->use_fqdn = TRUE;
|
|
} else {
|
|
nm_assert_not_reached();
|
|
}
|
|
|
|
if (!config->hostname && config->send_hostname) {
|
|
const char *hostname;
|
|
gs_free char *hostname_tmp = NULL;
|
|
|
|
hostname = nm_hostname_manager_get_static_hostname(nm_hostname_manager_get());
|
|
|
|
if (nm_utils_is_specific_hostname(hostname)) {
|
|
if (config->addr_family == AF_INET) {
|
|
char *dot;
|
|
|
|
hostname_tmp = g_strdup(hostname);
|
|
dot = strchr(hostname_tmp, '.');
|
|
if (dot)
|
|
*dot = '\0';
|
|
}
|
|
config->hostname = hostname_tmp ? g_steal_pointer(&hostname_tmp) : g_strdup(hostname);
|
|
}
|
|
}
|
|
|
|
if (config->hostname) {
|
|
if (!config->send_hostname) {
|
|
nm_clear_g_free((gpointer *) &config->hostname);
|
|
} else if ((config->use_fqdn && !nm_sd_dns_name_is_valid(config->hostname))
|
|
|| (!config->use_fqdn && !nm_hostname_is_valid(config->hostname, FALSE))) {
|
|
nm_log_warn(LOGD_DHCP,
|
|
"dhcp%c: %s '%s' is invalid, will be ignored",
|
|
nm_utils_addr_family_to_char(config->addr_family),
|
|
config->use_fqdn ? "FQDN" : "hostname",
|
|
config->hostname);
|
|
nm_clear_g_free((gpointer *) &config->hostname);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
config_clear(NMDhcpClientConfig *config)
|
|
{
|
|
g_object_unref(config->l3cfg);
|
|
|
|
nm_clear_pointer(&config->hwaddr, g_bytes_unref);
|
|
nm_clear_pointer(&config->bcast_hwaddr, g_bytes_unref);
|
|
nm_clear_pointer(&config->vendor_class_identifier, g_bytes_unref);
|
|
nm_clear_pointer(&config->client_id, g_bytes_unref);
|
|
|
|
nm_clear_g_free((gpointer *) &config->iface);
|
|
nm_clear_g_free((gpointer *) &config->uuid);
|
|
nm_clear_g_free((gpointer *) &config->anycast_address);
|
|
nm_clear_g_free((gpointer *) &config->hostname);
|
|
nm_clear_g_free((gpointer *) &config->mud_url);
|
|
|
|
nm_clear_pointer((gpointer *) &config->reject_servers, g_strfreev);
|
|
|
|
if (config->addr_family == AF_INET) {
|
|
nm_clear_g_free((gpointer *) &config->v4.last_address);
|
|
}
|
|
}
|
|
|
|
int
|
|
nm_dhcp_client_get_addr_family(NMDhcpClient *self)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
return priv->config.addr_family;
|
|
}
|
|
|
|
const char *
|
|
nm_dhcp_client_get_iface(NMDhcpClient *self)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
return priv->config.iface;
|
|
}
|
|
|
|
NMDedupMultiIndex *
|
|
nm_dhcp_client_get_multi_idx(NMDhcpClient *self)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
return nm_l3cfg_get_multi_idx(priv->config.l3cfg);
|
|
}
|
|
|
|
int
|
|
nm_dhcp_client_get_ifindex(NMDhcpClient *self)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
return nm_l3cfg_get_ifindex(priv->config.l3cfg);
|
|
}
|
|
|
|
GBytes *
|
|
nm_dhcp_client_get_effective_client_id(NMDhcpClient *self)
|
|
{
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
return priv->effective_client_id;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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_CONFIG:
|
|
/* construct-only */
|
|
config_init(&priv->config, g_value_get_pointer(value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
nm_dhcp_client_init(NMDhcpClient *self)
|
|
{
|
|
NMDhcpClientPrivate *priv;
|
|
|
|
priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_DHCP_CLIENT, NMDhcpClientPrivate);
|
|
self->_priv = priv;
|
|
|
|
priv->pid = -1;
|
|
}
|
|
|
|
static void
|
|
dispose(GObject *object)
|
|
{
|
|
NMDhcpClient *self = NM_DHCP_CLIENT(object);
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
nm_dhcp_client_stop(self, FALSE);
|
|
|
|
watch_cleanup(self);
|
|
|
|
nm_clear_g_source_inst(&priv->no_lease_timeout_source);
|
|
nm_clear_g_source_inst(&priv->ipv6_lladdr_timeout_source);
|
|
nm_clear_pointer(&priv->effective_client_id, g_bytes_unref);
|
|
|
|
G_OBJECT_CLASS(nm_dhcp_client_parent_class)->dispose(object);
|
|
}
|
|
|
|
static void
|
|
finalize(GObject *object)
|
|
{
|
|
NMDhcpClient *self = NM_DHCP_CLIENT(object);
|
|
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
|
|
|
config_clear(&priv->config);
|
|
|
|
G_OBJECT_CLASS(nm_dhcp_client_parent_class)->finalize(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));
|
|
|
|
object_class->dispose = dispose;
|
|
object_class->finalize = finalize;
|
|
object_class->set_property = set_property;
|
|
|
|
client_class->stop = stop;
|
|
client_class->get_duid = get_duid;
|
|
|
|
obj_properties[PROP_CONFIG] =
|
|
g_param_spec_pointer(NM_DHCP_CLIENT_CONFIG,
|
|
"",
|
|
"",
|
|
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
|
|
|
|
signals[SIGNAL_NOTIFY] =
|
|
g_signal_new(NM_DHCP_CLIENT_NOTIFY,
|
|
G_OBJECT_CLASS_TYPE(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_VOID__POINTER,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_POINTER /* const NMDhcpClientNotifyData *notify_data */);
|
|
}
|