From 6accbd3ec0e42d8633bbde4d47ed7bfe854e7e0b Mon Sep 17 00:00:00 2001 From: Michael Biebl Date: Fri, 12 Aug 2022 19:21:11 +0200 Subject: New upstream version 1.38.4 --- src/core/dhcp/nm-dhcp-client.c | 740 ++++++++++++++++++++++++++--------------- 1 file changed, 463 insertions(+), 277 deletions(-) (limited to 'src/core/dhcp/nm-dhcp-client.c') diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c index 2bfd7e01..00a2d207 100644 --- a/src/core/dhcp/nm-dhcp-client.c +++ b/src/core/dhcp/nm-dhcp-client.c @@ -32,7 +32,10 @@ /*****************************************************************************/ -enum { SIGNAL_NOTIFY, LAST_SIGNAL }; +enum { + SIGNAL_NOTIFY, + LAST_SIGNAL, +}; static guint signals[LAST_SIGNAL] = {0}; @@ -42,18 +45,30 @@ 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; - guint watch_id; - NMDhcpState state; - bool iaid_explicit : 1; - bool is_stopped : 1; + + union { + struct { + struct { + GDBusMethodInvocation *invocation; + } bound; + } v4; + struct { + GSource *lladdr_timeout_source; + GSource *dad_timeout_source; + } v6; + }; + struct { gulong id; bool wait_dhcp_commit : 1; + bool wait_ipv6_dad : 1; bool wait_ll_address : 1; } l3cfg_notify; + + pid_t pid; + bool is_stopped : 1; } NMDhcpClientPrivate; G_DEFINE_ABSTRACT_TYPE(NMDhcpClient, nm_dhcp_client, G_TYPE_OBJECT) @@ -62,6 +77,13 @@ G_DEFINE_ABSTRACT_TYPE(NMDhcpClient, nm_dhcp_client, G_TYPE_OBJECT) /*****************************************************************************/ +static gboolean _dhcp_client_accept(NMDhcpClient *self, const NML3ConfigData *l3cd, GError **error); + +_nm_unused static gboolean _dhcp_client_decline(NMDhcpClient *self, + const NML3ConfigData *l3cd, + const char *error_message, + GError **error); + static void l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcpClient *self); @@ -72,47 +94,76 @@ G_STATIC_ASSERT(!(((pid_t) -1) > 0)); /*****************************************************************************/ -static void -_emit_notify(NMDhcpClient *self, const NMDhcpClientNotifyData *notify_data) +NM_UTILS_LOOKUP_STR_DEFINE(nm_dhcp_client_event_type_to_string, + NMDhcpClientEventType, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(NULL), + NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_CLIENT_EVENT_TYPE_BOUND, "bound"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_CLIENT_EVENT_TYPE_EXPIRE, "expire"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_CLIENT_EVENT_TYPE_EXTENDED, "extended"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_CLIENT_EVENT_TYPE_FAIL, "fail"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_CLIENT_EVENT_TYPE_TERMINATED, + "terminated"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_CLIENT_EVENT_TYPE_TIMEOUT, "timeout"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_CLIENT_EVENT_TYPE_UNSPECIFIED, + "unspecified"), ); + +/*****************************************************************************/ + +int +nm_dhcp_client_get_addr_family(NMDhcpClient *self) { - g_signal_emit(G_OBJECT(self), signals[SIGNAL_NOTIFY], 0, notify_data); + 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); -static void -connect_l3cfg_notify(NMDhcpClient *self) + return priv->config.iface; +} + +NMDedupMultiIndex * +nm_dhcp_client_get_multi_idx(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; + return nm_l3cfg_get_multi_idx(priv->config.l3cfg); +} - if (!do_connect) { - nm_clear_g_signal_handler(priv->config.l3cfg, &priv->l3cfg_notify.id); - return; - } +int +nm_dhcp_client_get_ifindex(NMDhcpClient *self) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); - 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); - } + return nm_l3cfg_get_ifindex(priv->config.l3cfg); } -pid_t -nm_dhcp_client_get_pid(NMDhcpClient *self) +const NMDhcpClientConfig * +nm_dhcp_client_get_config(NMDhcpClient *self) { - g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), -1); + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); - return NM_DHCP_CLIENT_GET_PRIVATE(self)->pid; + return &priv->config; } +GBytes * +nm_dhcp_client_get_effective_client_id(NMDhcpClient *self) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + return priv->effective_client_id; +} + +/*****************************************************************************/ + void nm_dhcp_client_set_effective_client_id(NMDhcpClient *self, GBytes *client_id) { - NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + 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); @@ -123,72 +174,63 @@ nm_dhcp_client_set_effective_client_id(NMDhcpClient *self, GBytes *client_id) return; g_bytes_unref(priv->effective_client_id); - priv->effective_client_id = client_id; - if (client_id) - g_bytes_ref(client_id); + priv->effective_client_id = nm_g_bytes_ref(client_id); - { - gs_free char *s = NULL; + _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"); +} - _LOGT("%s: set %s", - priv->config.addr_family == AF_INET6 ? "duid" : "client-id", - priv->effective_client_id - ? (s = nm_dhcp_utils_duid_to_string(priv->effective_client_id)) - : "default"); - } +/*****************************************************************************/ + +static void +_emit_notify(NMDhcpClient *self, const NMDhcpClientNotifyData *notify_data) +{ + g_signal_emit(G_OBJECT(self), signals[SIGNAL_NOTIFY], 0, notify_data); } /*****************************************************************************/ -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 NMDhcpState -reason_to_state(NMDhcpClient *self, const char *iface, const char *reason) +static void +l3_cfg_notify_check_connected(NMDhcpClient *self) { - if (g_ascii_strcasecmp(reason, "bound") == 0 || g_ascii_strcasecmp(reason, "bound6") == 0 - || g_ascii_strcasecmp(reason, "static") == 0) - return NM_DHCP_STATE_BOUND; - else if (g_ascii_strcasecmp(reason, "renew") == 0 || g_ascii_strcasecmp(reason, "renew6") == 0 - || g_ascii_strcasecmp(reason, "reboot") == 0 - || g_ascii_strcasecmp(reason, "rebind") == 0 - || g_ascii_strcasecmp(reason, "rebind6") == 0) - return NM_DHCP_STATE_EXTENDED; - else if (g_ascii_strcasecmp(reason, "timeout") == 0) - return NM_DHCP_STATE_TIMEOUT; - else if (g_ascii_strcasecmp(reason, "nak") == 0 || g_ascii_strcasecmp(reason, "expire") == 0 - || g_ascii_strcasecmp(reason, "expire6") == 0) - return NM_DHCP_STATE_EXPIRE; - else if (g_ascii_strcasecmp(reason, "end") == 0 || g_ascii_strcasecmp(reason, "stop") == 0 - || g_ascii_strcasecmp(reason, "stopped") == 0) - return NM_DHCP_STATE_DONE; - else if (g_ascii_strcasecmp(reason, "fail") == 0 || g_ascii_strcasecmp(reason, "abend") == 0) - return NM_DHCP_STATE_FAIL; - else if (g_ascii_strcasecmp(reason, "preinit") == 0) - return NM_DHCP_STATE_NOOP; - - _LOGD("unmapped DHCP state '%s'", reason); - return NM_DHCP_STATE_UNKNOWN; + 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 + | priv->l3cfg_notify.wait_ipv6_dad; + + 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); + } } /*****************************************************************************/ +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(&priv->watch_id); + nm_clear_g_source_inst(&priv->watch_source); } void @@ -225,6 +267,8 @@ stop(NMDhcpClient *self, gboolean release) priv->pid = -1; } +/*****************************************************************************/ + static gboolean _no_lease_timeout(gpointer user_data) { @@ -237,19 +281,12 @@ _no_lease_timeout(gpointer user_data) &((NMDhcpClientNotifyData){ .notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_NO_LEASE_TIMEOUT, })); - return G_SOURCE_CONTINUE; -} -const NMDhcpClientConfig * -nm_dhcp_client_get_config(NMDhcpClient *self) -{ - NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); - - return &priv->config; + return G_SOURCE_CONTINUE; } static void -schedule_no_lease_timeout(NMDhcpClient *self) +_no_lease_timeout_schedule(NMDhcpClient *self) { NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); @@ -267,29 +304,55 @@ schedule_no_lease_timeout(NMDhcpClient *self) } } +/*****************************************************************************/ + void -nm_dhcp_client_set_state(NMDhcpClient *self, NMDhcpState new_state, const NML3ConfigData *l3cd) +_nm_dhcp_client_notify(NMDhcpClient *self, + NMDhcpClientEventType client_event_type, + 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; - - g_return_if_fail(NM_IS_DHCP_CLIENT(self)); - - if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED)) { - g_return_if_fail(NM_IS_L3_CONFIG_DATA(l3cd)); - g_return_if_fail(nm_l3_config_data_get_dhcp_lease(l3cd, priv->config.addr_family)); - } else - g_return_if_fail(!l3cd); + char sbuf1[NM_HASH_OBFUSCATE_PTR_STR_BUF_SIZE]; + + nm_assert(NM_IN_SET(client_event_type, + NM_DHCP_CLIENT_EVENT_TYPE_UNSPECIFIED, + NM_DHCP_CLIENT_EVENT_TYPE_BOUND, + NM_DHCP_CLIENT_EVENT_TYPE_EXTENDED, + NM_DHCP_CLIENT_EVENT_TYPE_TIMEOUT, + NM_DHCP_CLIENT_EVENT_TYPE_EXPIRE, + NM_DHCP_CLIENT_EVENT_TYPE_FAIL, + NM_DHCP_CLIENT_EVENT_TYPE_TERMINATED)); + nm_assert((client_event_type >= NM_DHCP_CLIENT_EVENT_TYPE_TIMEOUT) + == NM_IN_SET(client_event_type, + NM_DHCP_CLIENT_EVENT_TYPE_TIMEOUT, + NM_DHCP_CLIENT_EVENT_TYPE_EXPIRE, + NM_DHCP_CLIENT_EVENT_TYPE_FAIL, + NM_DHCP_CLIENT_EVENT_TYPE_TERMINATED)); + nm_assert((!!l3cd) + == NM_IN_SET(client_event_type, + NM_DHCP_CLIENT_EVENT_TYPE_BOUND, + NM_DHCP_CLIENT_EVENT_TYPE_EXTENDED)); + + nm_assert(!l3cd || NM_IS_L3_CONFIG_DATA(l3cd)); + nm_assert(!l3cd || nm_l3_config_data_get_dhcp_lease(l3cd, priv->config.addr_family)); + + _LOGT("notify: event=%s%s%s", + nm_dhcp_client_event_type_to_string(client_event_type), + NM_PRINT_FMT_QUOTED2(l3cd, ", l3cd=", NM_HASH_OBFUSCATE_PTR_STR(l3cd, sbuf1), "")); if (l3cd) nm_l3_config_data_seal(l3cd); - if (new_state >= NM_DHCP_STATE_TIMEOUT) + if (client_event_type >= NM_DHCP_CLIENT_EVENT_TYPE_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)) { l3cd = nm_l3_config_data_seal(l3cd_merged); } @@ -302,11 +365,11 @@ nm_dhcp_client_set_state(NMDhcpClient *self, NMDhcpState new_state, const NML3Co nm_clear_g_source_inst(&priv->no_lease_timeout_source); } else { if (priv->l3cd) - schedule_no_lease_timeout(self); + _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 + * The internals like the state should not be exposed (or possibly dropped in large * parts). */ nm_l3_config_data_reset(&priv->l3cd, l3cd); @@ -353,7 +416,7 @@ nm_dhcp_client_set_state(NMDhcpClient *self, NMDhcpState new_state, const NML3Co * 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 + * If ACD fails, we call _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), @@ -367,13 +430,24 @@ nm_dhcp_client_set_state(NMDhcpClient *self, NMDhcpState new_state, const NML3Co * 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 + if (client_event_type == NM_DHCP_CLIENT_EVENT_TYPE_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); + + if (!priv->l3cfg_notify.wait_dhcp_commit && priv->l3cd) { + gs_free_error GError *error = NULL; + + _LOGD("accept lease right away"); + if (!_dhcp_client_accept(self, priv->l3cd, &error)) { + _LOGD("accept failed: %s", error->message); + /* Unclear why this happened, or what to do about it. Just proceed. */ + } + } + + l3_cfg_notify_check_connected(self); { const NMDhcpClientNotifyData notify_data = { @@ -396,14 +470,15 @@ daemon_watch_cb(GPid pid, int status, gpointer user_data) NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); gs_free char *desc = NULL; - g_return_if_fail(priv->watch_id); - priv->watch_id = 0; + 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); + _nm_dhcp_client_notify(self, NM_DHCP_CLIENT_EVENT_TYPE_TERMINATED, NULL); } void @@ -414,8 +489,8 @@ nm_dhcp_client_watch_child(NMDhcpClient *self, pid_t pid) g_return_if_fail(priv->pid == -1); priv->pid = pid; - g_return_if_fail(priv->watch_id == 0); - priv->watch_id = g_child_watch_add(pid, daemon_watch_cb, self); + g_return_if_fail(!priv->watch_source); + priv->watch_source = nm_g_child_watch_add_source(pid, daemon_watch_cb, self); } void @@ -429,71 +504,74 @@ nm_dhcp_client_stop_watch_child(NMDhcpClient *self, pid_t pid) watch_cleanup(self); } -gboolean -nm_dhcp_client_start_ip4(NMDhcpClient *self, GError **error) +static gboolean +_accept(NMDhcpClient *self, const NML3ConfigData *l3cd, GError **error) { - NMDhcpClientPrivate *priv; - - g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE); + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); - 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); + if (!NM_IS_IPv4(priv->config.addr_family)) + return TRUE; - schedule_no_lease_timeout(self); + if (!priv->v4.bound.invocation) + return TRUE; - return NM_DHCP_CLIENT_GET_CLASS(self)->ip4_start(self, error); + g_dbus_method_invocation_return_value(g_steal_pointer(&priv->v4.bound.invocation), NULL); + return TRUE; } -gboolean -nm_dhcp_client_accept(NMDhcpClient *self, GError **error) +static gboolean +_dhcp_client_accept(NMDhcpClient *self, const NML3ConfigData *l3cd, GError **error) { - NMDhcpClientPrivate *priv; + NMDhcpClientClass *klass; g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE); + nm_assert(l3cd); - priv = NM_DHCP_CLIENT_GET_PRIVATE(self); - - g_return_val_if_fail(priv->l3cd, FALSE); + klass = NM_DHCP_CLIENT_GET_CLASS(self); - if (NM_DHCP_CLIENT_GET_CLASS(self)->accept) { - return NM_DHCP_CLIENT_GET_CLASS(self)->accept(self, error); - } + g_return_val_if_fail(NM_DHCP_CLIENT_GET_PRIVATE(self)->l3cd, FALSE); - return TRUE; + return klass->accept(self, l3cd, error); } -gboolean -nm_dhcp_client_can_accept(NMDhcpClient *self) +static gboolean +decline(NMDhcpClient *self, const NML3ConfigData *l3cd, const char *error_message, GError **error) { - gboolean can_accept; - - g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE); + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); - can_accept = !!(NM_DHCP_CLIENT_GET_CLASS(self)->accept); + if (!NM_IS_IPv4(priv->config.addr_family)) + return TRUE; - nm_assert(can_accept == (!!(NM_DHCP_CLIENT_GET_CLASS(self)->decline))); + if (!priv->v4.bound.invocation) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "calling decline in unexpected script state"); + return FALSE; + } - return can_accept; + g_dbus_method_invocation_return_error(g_steal_pointer(&priv->v4.bound.invocation), + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "acd failed"); + return TRUE; } -gboolean -nm_dhcp_client_decline(NMDhcpClient *self, const char *error_message, GError **error) +static gboolean +_dhcp_client_decline(NMDhcpClient *self, + const NML3ConfigData *l3cd, + const char *error_message, + GError **error) { - NMDhcpClientPrivate *priv; + NMDhcpClientClass *klass; g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE); + nm_assert(l3cd); - priv = NM_DHCP_CLIENT_GET_PRIVATE(self); - - g_return_val_if_fail(priv->l3cd, FALSE); + klass = NM_DHCP_CLIENT_GET_CLASS(self); - if (NM_DHCP_CLIENT_GET_CLASS(self)->decline) { - return NM_DHCP_CLIENT_GET_CLASS(self)->decline(self, error_message, error); - } + g_return_val_if_fail(NM_DHCP_CLIENT_GET_PRIVATE(self)->l3cd, FALSE); - return TRUE; + return klass->decline(self, l3cd, error_message, error); } static GBytes * @@ -508,7 +586,7 @@ 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); + nm_clear_g_source_inst(&priv->v6.lladdr_timeout_source); _emit_notify( self, @@ -519,6 +597,23 @@ ipv6_lladdr_timeout(gpointer user_data) return G_SOURCE_CONTINUE; } +static gboolean +ipv6_dad_timeout(gpointer user_data) +{ + NMDhcpClient *self = user_data; + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + nm_clear_g_source_inst(&priv->v6.dad_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 IPv6 DAD to complete", + })); + return G_SOURCE_CONTINUE; +} + static const NMPlatformIP6Address * ipv6_lladdr_find(NMDhcpClient *self) { @@ -528,6 +623,8 @@ ipv6_lladdr_find(NMDhcpClient *self) NMDedupMultiIter iter; const NMPObject *obj; + nm_assert(!NM_IS_IPv4(priv->config.addr_family)); + l3cfg = priv->config.l3cfg; nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, nm_l3cfg_get_ifindex(l3cfg)); @@ -544,6 +641,37 @@ ipv6_lladdr_find(NMDhcpClient *self) return NULL; } +static const NMPlatformIP6Address * +ipv6_tentative_addr_find(NMDhcpClient *self) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + NMDedupMultiIter iter; + const NMPlatformIP6Address *addr; + NML3Cfg *l3cfg = priv->config.l3cfg; + + /* For each address in the lease, check whether it's tentative + * in platform. */ + nm_l3_config_data_iter_ip6_address_for_each (&iter, priv->l3cd, &addr) { + const NMPlatformIP6Address *pladdr; + NMPObject needle; + + nmp_object_stackinit_id_ip6_address(&needle, nm_l3cfg_get_ifindex(l3cfg), &addr->address); + pladdr = NMP_OBJECT_CAST_IP6_ADDRESS(nm_platform_lookup_obj(nm_l3cfg_get_platform(l3cfg), + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + &needle)); + if (!pladdr) { + /* Address was removed from platform */ + continue; + } + + if (NM_FLAGS_HAS(pladdr->n_ifa_flags, IFA_F_TENTATIVE) + && !NM_FLAGS_HAS(pladdr->n_ifa_flags, IFA_F_OPTIMISTIC)) + return pladdr; + } + + return NULL; +} + static void l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcpClient *self) { @@ -551,23 +679,19 @@ l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcp nm_assert(l3cfg == priv->config.l3cfg); - switch (notify_data->notify_type) { - case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE: - { + if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE + && priv->l3cfg_notify.wait_ll_address) { 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); + l3_cfg_notify_check_connected(self); + nm_clear_g_source_inst(&priv->v6.lladdr_timeout_source); - schedule_no_lease_timeout(self); + _no_lease_timeout_schedule(self); if (!NM_DHCP_CLIENT_GET_CLASS(self)->ip6_start(self, &addr->address, &error)) { _emit_notify(self, @@ -577,11 +701,30 @@ l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcp })); } } + } - break; + if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE + && priv->l3cfg_notify.wait_ipv6_dad) { + const NMPlatformIP6Address *tentative; + + tentative = ipv6_tentative_addr_find(self); + if (!tentative) { + _LOGD("addresses in the lease completed DAD"); + priv->l3cfg_notify.wait_ipv6_dad = FALSE; + nm_clear_g_source_inst(&priv->v6.dad_timeout_source); + l3_cfg_notify_check_connected(self); + _emit_notify( + self, + &((NMDhcpClientNotifyData){.notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE, + .lease_update = { + .l3cd = priv->l3cd, + .accepted = TRUE, + }})); + } } - case NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT: - { + + if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT + && priv->l3cfg_notify.wait_dhcp_commit) { const NML3ConfigData *committed_l3cd; NMDedupMultiIter ipconf_iter; const NMPlatformIPAddress *lease_address; @@ -592,9 +735,6 @@ l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcp * 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, @@ -610,20 +750,35 @@ l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcp address4->address, address4->plen, address4->peer_address)) - return; + goto wait_dhcp_commit_done; } else { const NMPlatformIP6Address *address6 = (const NMPlatformIP6Address *) lease_address; + const NMPlatformIP6Address *tentative; + char str[NM_UTILS_TO_STRING_BUFFER_SIZE]; if (!nm_l3_config_data_lookup_address_6(committed_l3cd, &address6->address)) - return; + goto wait_dhcp_commit_done; + + tentative = ipv6_tentative_addr_find(self); + if (tentative) { + priv->l3cfg_notify.wait_ipv6_dad = TRUE; + priv->v6.dad_timeout_source = + nm_g_timeout_add_seconds_source(30, ipv6_dad_timeout, self); + _LOGD("wait DAD for address %s", + nm_platform_ip6_address_to_string(tentative, str, sizeof(str))); + } else { + priv->l3cfg_notify.wait_ipv6_dad = FALSE; + nm_clear_g_source_inst(&priv->v6.dad_timeout_source); + } } priv->l3cfg_notify.wait_dhcp_commit = FALSE; - connect_l3cfg_notify(self); - _LOGD("accept address"); + l3_cfg_notify_check_connected(self); - if (!nm_dhcp_client_accept(self, &error)) { + _LOGD("accept lease"); + + if (!_dhcp_client_accept(self, priv->l3cd, &error)) { gs_free char *reason = g_strdup_printf("error accepting lease: %s", error->message); _emit_notify(self, @@ -631,58 +786,67 @@ l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcp .notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD, .it_looks_bad.reason = reason, })); - return; + goto wait_dhcp_commit_done; } - _emit_notify( - self, - &((NMDhcpClientNotifyData){.notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE, - .lease_update = { - .l3cd = priv->l3cd, - .accepted = TRUE, - }})); - break; - }; - default: - /* ignore */; + if (priv->config.addr_family == AF_INET || !priv->l3cfg_notify.wait_ipv6_dad) { + _emit_notify( + self, + &((NMDhcpClientNotifyData){.notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE, + .lease_update = { + .l3cd = priv->l3cd, + .accepted = TRUE, + }})); + } } +wait_dhcp_commit_done:; } gboolean -nm_dhcp_client_start_ip6(NMDhcpClient *self, GError **error) +nm_dhcp_client_start(NMDhcpClient *self, GError **error) { NMDhcpClientPrivate *priv; gs_unref_bytes GBytes *own_client_id = NULL; - const NMPlatformIP6Address *addr; + const NMPlatformIP6Address *addr = NULL; + int IS_IPv4; 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); + nm_assert(!priv->effective_client_id); - if (!priv->config.v6.enforce_duid) - own_client_id = NM_DHCP_CLIENT_GET_CLASS(self)->get_duid(self); + IS_IPv4 = NM_IS_IPv4(priv->config.addr_family); - nm_dhcp_client_set_effective_client_id(self, own_client_id ?: priv->config.client_id); + if (!IS_IPv4) { + if (!priv->config.v6.enforce_duid) + own_client_id = NM_DHCP_CLIENT_GET_CLASS(self)->get_duid(self); - 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; + 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; + l3_cfg_notify_check_connected(self); + priv->v6.lladdr_timeout_source = + nm_g_timeout_add_seconds_source(10, ipv6_lladdr_timeout, self); + return TRUE; + } } - schedule_no_lease_timeout(self); + _no_lease_timeout_schedule(self); + + if (IS_IPv4) + return NM_DHCP_CLIENT_GET_CLASS(self)->ip4_start(self, error); 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) { @@ -757,9 +921,17 @@ nm_dhcp_client_stop(NMDhcpClient *self, gboolean release) priv->is_stopped = TRUE; + if (NM_IS_IPv4(priv->config.addr_family) && priv->v4.bound.invocation) { + g_dbus_method_invocation_return_error(g_steal_pointer(&priv->v4.bound.invocation), + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "dhcp stopping"); + } + priv->l3cfg_notify.wait_dhcp_commit = FALSE; priv->l3cfg_notify.wait_ll_address = FALSE; - connect_l3cfg_notify(self); + priv->l3cfg_notify.wait_ipv6_dad = FALSE; + l3_cfg_notify_check_connected(self); /* Kill the DHCP client */ old_pid = priv->pid; @@ -770,7 +942,7 @@ nm_dhcp_client_stop(NMDhcpClient *self, gboolean release) _LOGI("canceled DHCP transaction"); nm_assert(priv->pid == -1); - nm_dhcp_client_set_state(self, NM_DHCP_STATE_TERMINATED, NULL); + _nm_dhcp_client_notify(self, NM_DHCP_CLIENT_EVENT_TYPE_TERMINATED, NULL); } /*****************************************************************************/ @@ -895,16 +1067,17 @@ nm_dhcp_client_emit_ipv6_prefix_delegated(NMDhcpClient *self, const NMPlatformIP } gboolean -nm_dhcp_client_handle_event(gpointer unused, - const char *iface, - int pid, - GVariant *options, - const char *reason, - NMDhcpClient *self) +nm_dhcp_client_handle_event(gpointer unused, + const char *iface, + int pid, + GVariant *options, + const char *reason, + GDBusMethodInvocation *invocation, + NMDhcpClient *self) { NMDhcpClientPrivate *priv; - guint32 new_state; - nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; + nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; + NMDhcpClientEventType client_event_type; NMPlatformIP6Address prefix = { 0, }; @@ -914,6 +1087,7 @@ nm_dhcp_client_handle_event(gpointer unused, 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); + g_return_val_if_fail(G_IS_DBUS_METHOD_INVOCATION(invocation), FALSE); priv = NM_DHCP_CLIENT_GET_PRIVATE(self); @@ -922,17 +1096,29 @@ nm_dhcp_client_handle_event(gpointer unused, if (priv->pid != pid) return FALSE; - new_state = reason_to_state(self, priv->config.iface, reason); - if (new_state == NM_DHCP_STATE_NOOP) - return TRUE; - - _LOGD("DHCP state '%s' -> '%s' (reason: '%s')", - nm_dhcp_state_to_string(priv->state), - nm_dhcp_state_to_string(new_state), - reason); - priv->state = new_state; + _LOGD("DHCP event (reason: '%s')", reason); + + if (NM_IN_STRSET_ASCII_CASE(reason, "preinit")) + goto out_handled; + + if (NM_IN_STRSET_ASCII_CASE(reason, "bound", "bound6", "static")) + client_event_type = NM_DHCP_CLIENT_EVENT_TYPE_BOUND; + else if (NM_IN_STRSET_ASCII_CASE(reason, "renew", "renew6", "reboot", "rebind", "rebind6")) + client_event_type = NM_DHCP_CLIENT_EVENT_TYPE_EXTENDED; + else if (NM_IN_STRSET_ASCII_CASE(reason, "timeout")) + client_event_type = NM_DHCP_CLIENT_EVENT_TYPE_TIMEOUT; + else if (NM_IN_STRSET_ASCII_CASE(reason, "nak", "expire", "expire6")) + client_event_type = NM_DHCP_CLIENT_EVENT_TYPE_EXPIRE; + else if (NM_IN_STRSET_ASCII_CASE(reason, "end", "stop", "stopped")) + client_event_type = NM_DHCP_CLIENT_EVENT_TYPE_TERMINATED; + else if (NM_IN_STRSET_ASCII_CASE(reason, "fail", "abend")) + client_event_type = NM_DHCP_CLIENT_EVENT_TYPE_FAIL; + else + client_event_type = NM_DHCP_CLIENT_EVENT_TYPE_UNSPECIFIED; - if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED)) { + if (NM_IN_SET(client_event_type, + NM_DHCP_CLIENT_EVENT_TYPE_BOUND, + NM_DHCP_CLIENT_EVENT_TYPE_EXTENDED)) { gs_unref_hashtable GHashTable *str_options = NULL; GVariantIter iter; const char *name; @@ -963,8 +1149,7 @@ nm_dhcp_client_handle_event(gpointer unused, str_options, priv->config.v6.info_only); } - } else - g_warn_if_reached(); + } if (l3cd) { nm_l3_config_data_set_dhcp_lease_from_options(l3cd, @@ -978,16 +1163,32 @@ nm_dhcp_client_handle_event(gpointer unused, * 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; + goto out_handled; } - /* Fail if no valid IP config was received */ - if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED) && !l3cd) { + if (NM_IN_SET(client_event_type, + NM_DHCP_CLIENT_EVENT_TYPE_BOUND, + NM_DHCP_CLIENT_EVENT_TYPE_EXTENDED) + && !l3cd) { + /* Fail if no valid IP config was received */ _LOGW("client bound but IP config not received"); - new_state = NM_DHCP_STATE_FAIL; + client_event_type = NM_DHCP_CLIENT_EVENT_TYPE_FAIL; } - nm_dhcp_client_set_state(self, new_state, l3cd); + if (priv->v4.bound.invocation) + g_dbus_method_invocation_return_value(g_steal_pointer(&priv->v4.bound.invocation), NULL); + + if (NM_IS_IPv4(priv->config.addr_family) + && NM_IN_SET(client_event_type, + NM_DHCP_CLIENT_EVENT_TYPE_BOUND, + NM_DHCP_CLIENT_EVENT_TYPE_EXTENDED)) + priv->v4.bound.invocation = g_steal_pointer(&invocation); + + _nm_dhcp_client_notify(self, client_event_type, l3cd); + +out_handled: + if (invocation) + g_dbus_method_invocation_return_value(invocation, NULL); return TRUE; } @@ -1023,21 +1224,26 @@ nm_dhcp_client_server_id_is_rejected(NMDhcpClient *self, gconstpointer addr) return FALSE; } +/*****************************************************************************/ + static void config_init(NMDhcpClientConfig *config, const NMDhcpClientConfig *src) { + nm_assert(config); + nm_assert(src); + nm_assert(config != src); + nm_assert_addr_family(src->addr_family); + *config = *src; + /* We must not return before un-aliasing all pointers in @config! */ + 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); + nm_g_bytes_ref(config->hwaddr); + nm_g_bytes_ref(config->bcast_hwaddr); + nm_g_bytes_ref(config->vendor_class_identifier); + nm_g_bytes_ref(config->client_id); config->iface = g_strdup(config->iface); config->uuid = g_strdup(config->uuid); @@ -1047,14 +1253,12 @@ config_init(NMDhcpClientConfig *config, const NMDhcpClientConfig *src) config->reject_servers = (const char *const *) nm_strv_dup(config->reject_servers, -1, TRUE); - if (config->addr_family == AF_INET) { + if (NM_IS_IPv4(config->addr_family)) config->v4.last_address = g_strdup(config->v4.last_address); - } else if (config->addr_family == AF_INET6) { + else { config->hwaddr = NULL; config->bcast_hwaddr = NULL; config->use_fqdn = TRUE; - } else { - nm_assert_not_reached(); } if (!config->hostname && config->send_hostname) { @@ -1114,46 +1318,6 @@ config_clear(NMDhcpClientConfig *config) } } -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 @@ -1165,6 +1329,21 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps case PROP_CONFIG: /* construct-only */ config_init(&priv->config, g_value_get_pointer(value)); + + /* I know, this is technically not necessary. It just feels nicer to + * explicitly initialize the respective union member. */ + if (NM_IS_IPv4(priv->config.addr_family)) { + priv->v4 = (typeof(priv->v4)){ + .bound = + { + .invocation = NULL, + }, + }; + } else { + priv->v6 = (typeof(priv->v6)){ + .lladdr_timeout_source = NULL, + }; + } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); @@ -1196,7 +1375,12 @@ dispose(GObject *object) watch_cleanup(self); nm_clear_g_source_inst(&priv->no_lease_timeout_source); - nm_clear_g_source_inst(&priv->ipv6_lladdr_timeout_source); + + if (!NM_IS_IPv4(priv->config.addr_family)) { + nm_clear_g_source_inst(&priv->v6.lladdr_timeout_source); + nm_clear_g_source_inst(&priv->v6.dad_timeout_source); + } + nm_clear_pointer(&priv->effective_client_id, g_bytes_unref); G_OBJECT_CLASS(nm_dhcp_client_parent_class)->dispose(object); @@ -1223,6 +1407,8 @@ nm_dhcp_client_class_init(NMDhcpClientClass *client_class) object_class->dispose = dispose; object_class->finalize = finalize; object_class->set_property = set_property; + client_class->accept = _accept; + client_class->decline = decline; client_class->stop = stop; client_class->get_duid = get_duid; -- cgit 1.3.0-6-gf8a5 From 0018d1f3cf71d680d7b6bceda55a5717244d8b26 Mon Sep 17 00:00:00 2001 From: Michael Biebl Date: Tue, 16 Aug 2022 18:24:19 +0200 Subject: New upstream version 1.39.90 --- src/core/dhcp/nm-dhcp-client.c | 745 +++++++++++++++++++++++++++++++++-------- 1 file changed, 604 insertions(+), 141 deletions(-) (limited to 'src/core/dhcp/nm-dhcp-client.c') diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c index 00a2d207..77cfeecf 100644 --- a/src/core/dhcp/nm-dhcp-client.c +++ b/src/core/dhcp/nm-dhcp-client.c @@ -32,6 +32,38 @@ /*****************************************************************************/ +/* This is how long we do ACD for each entry and reject new offers for + * the same address. Note that the maximum ACD timeout is limited to 30 seconds + * (NM_ACD_TIMEOUT_MAX_MSEC). + **/ +#define ACD_REGLIST_GRACE_PERIOD_MSEC 300000u + +G_STATIC_ASSERT(ACD_REGLIST_GRACE_PERIOD_MSEC > (NM_ACD_TIMEOUT_MAX_MSEC + 1000)); + +#define ACD_REGLIST_MAX_ENTRIES 30 + +/* To do ACD for an address (new lease), we will register a NML3ConfigData + * with l3cfg. After ACD completes, we still continue having NML3Cfg + * watch that address, for ACD_REGLIST_GRACE_PERIOD_MSEC. The reasons are: + * + * - the caller is supposed to actually configure the address right after + * ACD passed. We would not want to drop the ACD state before the caller + * got a chance to do that. + * - when ACD fails, we decline the address and expect the DHCP client + * to present a new lease. We may want to outright reject the address, + * if ACD is bad. Thus, we want to keep running ACD for the address a bit + * longer, so that future requests for the same address can be rejected. + * + * This data structure is used for tracking the registered ACD address. + */ +typedef struct { + const NML3ConfigData *l3cd; + gint64 expiry_msec; + in_addr_t addr; +} AcdRegListData; + +/*****************************************************************************/ + enum { SIGNAL_NOTIFY, LAST_SIGNAL, @@ -42,14 +74,47 @@ 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 *watch_source; - GBytes *effective_client_id; + NMDhcpClientConfig config; + + /* This is the "next" data. That is, the one what was received last via + * _nm_dhcp_client_notify(), but which is currently pending on ACD. */ + const NML3ConfigData *l3cd_next; + + /* This is the currently exposed data. It passed ACD (or no ACD was performed), + * and is set from l3cd_next. */ + const NML3ConfigData *l3cd_curr; + + GSource *no_lease_timeout_source; + GSource *watch_source; + GBytes *effective_client_id; union { struct { + struct { + NML3CfgCommitTypeHandle *l3cfg_commit_handle; + GSource *done_source; + + /* When we do ACD for a l3cd lease, we will keep running ACD for + * the grace period ACD_REGLIST_GRACE_PERIOD_MSEC, even if we already + * determined the state. There are two reasons for that: + * + * - after ACD completes we notify the lease to the user, who is supposed + * to configure the address in NML3Cfg. If we were already removing the + * ACD state from NML3Cfg, ACD might need to start over. Instead, when + * the caller tries to configure the address, ACD state is already good. + * + * - if we decline on ACD offer, we may want to keep running and + * select other offers. Offers for which we just failed ACD (within + * ACD_REGLIST_GRACE_PERIOD_MSEC) are rejected. See _nm_dhcp_client_accept_offer(). + * For that, we keep monitoring the ACD state for up to ACD_REGLIST_MAX_ENTRIES + * addresses, to not restart and select the same lease twice in a row. + */ + GArray *reglist; + GSource *reglist_timeout_source; + + in_addr_t addr; + NMOptionBool state; + } acd; struct { GDBusMethodInvocation *invocation; } bound; @@ -77,16 +142,22 @@ G_DEFINE_ABSTRACT_TYPE(NMDhcpClient, nm_dhcp_client, G_TYPE_OBJECT) /*****************************************************************************/ +#define L3CD_ACD_TAG(priv) (&(priv)->v4.acd.addr) + static gboolean _dhcp_client_accept(NMDhcpClient *self, const NML3ConfigData *l3cd, GError **error); -_nm_unused static gboolean _dhcp_client_decline(NMDhcpClient *self, - const NML3ConfigData *l3cd, - const char *error_message, - GError **error); +static gboolean _dhcp_client_decline(NMDhcpClient *self, + const NML3ConfigData *l3cd, + const char *error_message, + GError **error); static void l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcpClient *self); +static void _acd_reglist_timeout_reschedule(NMDhcpClient *self, gint64 now_msec); + +static void _acd_reglist_data_remove(NMDhcpClient *self, guint idx, gboolean do_log); + /*****************************************************************************/ /* we use pid=-1 for invalid PIDs. Ensure that pid_t can hold negative values. */ @@ -157,6 +228,16 @@ nm_dhcp_client_get_effective_client_id(NMDhcpClient *self) return priv->effective_client_id; } +NML3ConfigData * +nm_dhcp_client_create_l3cd(NMDhcpClient *self) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + return nm_l3_config_data_new(nm_l3cfg_get_multi_idx(priv->config.l3cfg), + nm_l3cfg_get_ifindex(priv->config.l3cfg), + NM_IP_CONFIG_SOURCE_DHCP); +} + /*****************************************************************************/ void @@ -200,7 +281,8 @@ l3_cfg_notify_check_connected(NMDhcpClient *self) gboolean do_connect; do_connect = priv->l3cfg_notify.wait_dhcp_commit | priv->l3cfg_notify.wait_ll_address - | priv->l3cfg_notify.wait_ipv6_dad; + | priv->l3cfg_notify.wait_ipv6_dad + | (NM_IS_IPv4(priv->config.addr_family) && priv->v4.acd.l3cfg_commit_handle); if (!do_connect) { nm_clear_g_signal_handler(priv->config.l3cfg, &priv->l3cfg_notify.id); @@ -306,6 +388,333 @@ _no_lease_timeout_schedule(NMDhcpClient *self) /*****************************************************************************/ +static void +_acd_state_reset(NMDhcpClient *self, gboolean forget_addr, gboolean forget_reglist) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + if (!NM_IS_IPv4(priv->config.addr_family)) + return; + + if (priv->v4.acd.addr != INADDR_ANY) { + nm_l3cfg_commit_type_clear(priv->config.l3cfg, &priv->v4.acd.l3cfg_commit_handle); + l3_cfg_notify_check_connected(self); + nm_clear_g_source_inst(&priv->v4.acd.done_source); + if (forget_addr) { + priv->v4.acd.addr = INADDR_ANY; + priv->v4.acd.state = NM_OPTION_BOOL_DEFAULT; + } + } else + nm_assert(priv->v4.acd.state == NM_OPTION_BOOL_DEFAULT); + + if (forget_reglist) { + guint n; + + while ((n = nm_g_array_len(priv->v4.acd.reglist)) > 0) + _acd_reglist_data_remove(self, n - 1, TRUE); + } + + nm_assert(!priv->v4.acd.l3cfg_commit_handle); + nm_assert(!priv->v4.acd.done_source); + nm_assert(!forget_reglist + || !nm_l3cfg_remove_config_all(priv->config.l3cfg, L3CD_ACD_TAG(priv))); +} + +static gboolean +_acd_complete_on_idle_cb(gpointer user_data) +{ + NMDhcpClient *self = user_data; + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + + nm_assert(NM_IS_IPv4(priv->config.addr_family)); + nm_assert(priv->v4.acd.addr != INADDR_ANY); + nm_assert(!priv->v4.acd.l3cfg_commit_handle); + nm_assert(priv->l3cd_next); + + _acd_state_reset(self, FALSE, FALSE); + + _nm_dhcp_client_notify(self, NM_DHCP_CLIENT_EVENT_TYPE_BOUND, priv->l3cd_next); + + return G_SOURCE_CONTINUE; +} + +#define _acd_reglist_data_get(priv, idx) \ + nm_g_array_index_p((priv)->v4.acd.reglist, AcdRegListData, (idx)) + +static guint +_acd_reglist_data_find(NMDhcpClientPrivate *priv, in_addr_t addr_needle) +{ + const guint n = nm_g_array_len(priv->v4.acd.reglist); + guint i; + + nm_assert(addr_needle != INADDR_ANY); + + for (i = 0; i < n; i++) { + AcdRegListData *reglist_data = _acd_reglist_data_get(priv, i); + + if (reglist_data->addr == addr_needle) + return i; + } + return G_MAXUINT; +} + +static void +_acd_reglist_data_remove(NMDhcpClient *self, guint idx, gboolean do_log) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + AcdRegListData *reglist_data; + + nm_assert(idx < nm_g_array_len(priv->v4.acd.reglist)); + + reglist_data = _acd_reglist_data_get(priv, idx); + + if (do_log) { + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + + _LOGD("acd: drop check for address %s (l3cd " NM_HASH_OBFUSCATE_PTR_FMT ")", + _nm_utils_inet4_ntop(reglist_data->addr, sbuf_addr), + NM_HASH_OBFUSCATE_PTR(reglist_data->l3cd)); + } + + if (!nm_l3cfg_remove_config(priv->config.l3cfg, L3CD_ACD_TAG(priv), reglist_data->l3cd)) + nm_assert_not_reached(); + + nm_clear_l3cd(®list_data->l3cd); + + nm_l3cfg_commit_on_idle_schedule(priv->config.l3cfg, NM_L3_CFG_COMMIT_TYPE_UPDATE); + + g_array_remove_index(priv->v4.acd.reglist, idx); + + if (priv->v4.acd.reglist->len == 0) { + nm_clear_pointer(&priv->v4.acd.reglist, g_array_unref); + nm_clear_g_source_inst(&priv->v4.acd.reglist_timeout_source); + } +} + +static gboolean +_acd_reglist_timeout_cb(gpointer user_data) +{ + NMDhcpClient *self = user_data; + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + gint64 now_msec; + + nm_clear_g_source_inst(&priv->v4.acd.reglist_timeout_source); + + now_msec = nm_utils_get_monotonic_timestamp_msec(); + + while (nm_g_array_len(priv->v4.acd.reglist) > 0) { + AcdRegListData *reglist_data = _acd_reglist_data_get(priv, 0); + + if (reglist_data->expiry_msec > now_msec) + break; + + _acd_reglist_data_remove(self, 0, TRUE); + } + + _acd_reglist_timeout_reschedule(self, now_msec); + + return G_SOURCE_CONTINUE; +} + +static void +_acd_reglist_timeout_reschedule(NMDhcpClient *self, gint64 now_msec) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + AcdRegListData *reglist_data; + + if (nm_g_array_len(priv->v4.acd.reglist) == 0) { + nm_assert(!priv->v4.acd.reglist_timeout_source); + return; + } + + if (priv->v4.acd.reglist_timeout_source) { + /* already pending. As we only add new elements with a *later* + * expiry, we don't need to ever cancel a pending timer. Worst + * case, the timer fires, and there is nothing to do and we + * reschedule. */ + return; + } + + now_msec = nm_utils_get_monotonic_timestamp_msec(); + + reglist_data = _acd_reglist_data_get(priv, 0); + + nm_assert(reglist_data->expiry_msec > now_msec); + + priv->v4.acd.reglist_timeout_source = + nm_g_timeout_add_source(reglist_data->expiry_msec - now_msec, + _acd_reglist_timeout_cb, + self); +} + +static void +_acd_check_lease(NMDhcpClient *self, NMOptionBool *out_acd_state) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + in_addr_t addr; + gboolean addr_changed = FALSE; + guint idx; + gint64 now_msec; + + if (!NM_IS_IPv4(priv->config.addr_family)) + goto handle_no_acd; + + if (!priv->l3cd_next) + goto handle_no_acd; + + /* an IPv4 lease is always expected to have exactly one address. */ + nm_assert(nm_l3_config_data_get_num_addresses(priv->l3cd_next, AF_INET) == 1); + + if (priv->config.v4.acd_timeout_msec == 0) + goto handle_no_acd; + + addr = NMP_OBJECT_CAST_IP4_ADDRESS( + nm_l3_config_data_get_first_obj(priv->l3cd_next, NMP_OBJECT_TYPE_IP4_ADDRESS, NULL)) + ->address; + nm_assert(addr != INADDR_ANY); + + nm_clear_g_source_inst(&priv->v4.acd.done_source); + + if (priv->v4.acd.state != NM_OPTION_BOOL_DEFAULT && priv->v4.acd.addr == addr) { + /* the ACD state is already determined. Return right away. */ + nm_assert(!priv->v4.acd.l3cfg_commit_handle); + *out_acd_state = !!priv->v4.acd.state; + return; + } + + if (priv->v4.acd.addr != addr) { + addr_changed = TRUE; + priv->v4.acd.addr = addr; + } + + _LOGD("acd: %s check for address %s (timeout %u msec, l3cd " NM_HASH_OBFUSCATE_PTR_FMT ")", + addr_changed ? "add" : "update", + _nm_utils_inet4_ntop(addr, sbuf_addr), + priv->config.v4.acd_timeout_msec, + NM_HASH_OBFUSCATE_PTR(priv->l3cd_next)); + + priv->v4.acd.state = NM_OPTION_BOOL_DEFAULT; + + if (nm_l3cfg_add_config(priv->config.l3cfg, + L3CD_ACD_TAG(priv), + FALSE, + priv->l3cd_next, + NM_L3CFG_CONFIG_PRIORITY_IPV4LL, + 0, + 0, + NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4, + NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, + 0, + 0, + NM_DNS_PRIORITY_DEFAULT_NORMAL, + NM_DNS_PRIORITY_DEFAULT_NORMAL, + NM_L3_ACD_DEFEND_TYPE_ONCE, + NM_MIN(priv->config.v4.acd_timeout_msec, NM_ACD_TIMEOUT_MAX_MSEC), + NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD, + NM_L3_CONFIG_MERGE_FLAGS_NONE)) + addr_changed = TRUE; + + if (!priv->v4.acd.reglist) + priv->v4.acd.reglist = g_array_new(FALSE, FALSE, sizeof(AcdRegListData)); + + idx = _acd_reglist_data_find(priv, addr); + + now_msec = nm_utils_get_monotonic_timestamp_msec(); + + g_array_append_val(priv->v4.acd.reglist, + ((AcdRegListData){ + .l3cd = nm_l3_config_data_ref(priv->l3cd_next), + .addr = addr, + .expiry_msec = now_msec + ACD_REGLIST_GRACE_PERIOD_MSEC, + })); + + if (idx != G_MAXUINT) { + /* we already tracked this "addr". We don't need to track it twice, + * forget about this one. This also has the effect, that we will + * always append the new entry to the list (so the list + * stays sorted by the increasing timestamp). */ + _acd_reglist_data_remove(self, idx, FALSE); + } + + if (priv->v4.acd.reglist->len > ACD_REGLIST_MAX_ENTRIES) { + /* rate limit how many addresses we track for ACD. */ + _acd_reglist_data_remove(self, 0, TRUE); + } + + _acd_reglist_timeout_reschedule(self, now_msec); + + if (!priv->v4.acd.l3cfg_commit_handle) { + priv->v4.acd.l3cfg_commit_handle = + nm_l3cfg_commit_type_register(priv->config.l3cfg, + NM_L3_CFG_COMMIT_TYPE_UPDATE, + NULL, + "dhcp4-acd"); + l3_cfg_notify_check_connected(self); + } + + if (addr_changed) + nm_l3cfg_commit_on_idle_schedule(priv->config.l3cfg, NM_L3_CFG_COMMIT_TYPE_AUTO); + + /* ACD is started/pending... */ + nm_assert(priv->v4.acd.addr != INADDR_ANY); + nm_assert(priv->v4.acd.state == NM_OPTION_BOOL_DEFAULT); + nm_assert(priv->v4.acd.l3cfg_commit_handle); + nm_assert(priv->l3cfg_notify.id); + *out_acd_state = NM_OPTION_BOOL_DEFAULT; + return; + +handle_no_acd: + /* Indicate that ACD is good (or disabled) by returning TRUE. */ + _acd_state_reset(self, TRUE, FALSE); + *out_acd_state = NM_OPTION_BOOL_TRUE; + return; +} + +/*****************************************************************************/ + +gboolean +_nm_dhcp_client_accept_offer(NMDhcpClient *self, gconstpointer p_yiaddr) +{ + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + NMIPAddr yiaddr; + const NML3AcdAddrInfo *acd_info; + + if (!NM_IS_IPv4(priv->config.addr_family)) + return nm_assert_unreachable_val(FALSE); + + if (priv->config.v4.acd_timeout_msec == 0) { + /* ACD is disabled. Note that we might track the address for other + * reasons and have information about the ACD state below. But + * with ACD disabled, we always ignore that information. */ + return TRUE; + } + + nm_ip_addr_set(priv->config.addr_family, &yiaddr, p_yiaddr); + + /* Note that once we do ACD for a certain address, even after completing + * it, we keep the l3cd registered in NML3Cfg for ACD_REGLIST_GRACE_PERIOD_MSEC + * The idea is, that we don't yet turn off ACD for a grace period, so that + * we can avoid selecting the same lease again. + * + * Note that we even check whether we have an ACD state if priv->v4.acd.reglist + * is empty. Maybe for odd reasons, we track ACD for the address already. */ + + acd_info = nm_l3cfg_get_acd_addr_info(priv->config.l3cfg, yiaddr.addr4); + + if (!acd_info) + return TRUE; + + if (!NM_IN_SET(acd_info->state, NM_L3_ACD_ADDR_STATE_USED, NM_L3_ACD_ADDR_STATE_CONFLICT)) + return TRUE; + + _LOGD("offered lease rejected: address %s failed ACD check", + _nm_utils_inet4_ntop(yiaddr.addr4, sbuf_addr)); + + return FALSE; +} + void _nm_dhcp_client_notify(NMDhcpClient *self, NMDhcpClientEventType client_event_type, @@ -313,6 +722,8 @@ _nm_dhcp_client_notify(NMDhcpClient *self, { NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); GHashTable *options; + gboolean l3cd_changed; + NMOptionBool acd_state; const int IS_IPv4 = NM_IS_IPv4(priv->config.addr_family); nm_auto_unref_l3cd const NML3ConfigData *l3cd_merged = NULL; char sbuf1[NM_HASH_OBFUSCATE_PTR_STR_BUF_SIZE]; @@ -343,8 +754,7 @@ _nm_dhcp_client_notify(NMDhcpClient *self, nm_dhcp_client_event_type_to_string(client_event_type), NM_PRINT_FMT_QUOTED2(l3cd, ", l3cd=", NM_HASH_OBFUSCATE_PTR_STR(l3cd, sbuf1), "")); - if (l3cd) - nm_l3_config_data_seal(l3cd); + nm_l3_config_data_seal(l3cd); if (client_event_type >= NM_DHCP_CLIENT_EVENT_TYPE_TIMEOUT) watch_cleanup(self); @@ -353,33 +763,40 @@ _nm_dhcp_client_notify(NMDhcpClient *self, /* 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)) { + if (nm_dhcp_utils_merge_new_dhcp6_lease(priv->l3cd_next, 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); - } + } else + _no_lease_timeout_schedule(self); - /* FIXME(l3cfg:dhcp): the API of NMDhcpClient is changing to expose a simpler API. - * The internals like the state should not be exposed (or possibly dropped in large - * parts). */ + l3cd_changed = nm_l3_config_data_reset(&priv->l3cd_next, l3cd); - nm_l3_config_data_reset(&priv->l3cd, l3cd); + _acd_check_lease(self, &acd_state); - options = l3cd ? nm_dhcp_lease_get_options( - nm_l3_config_data_get_dhcp_lease(l3cd, priv->config.addr_family)) - : NULL; + options = priv->l3cd_next ? nm_dhcp_lease_get_options( + nm_l3_config_data_get_dhcp_lease(priv->l3cd_next, priv->config.addr_family)) + : NULL; + + 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_next ? "new lease" : "no lease", + NM_PRINT_FMT_QUOTED2(addr, ", address=", addr, ""), + acd_state == NM_OPTION_BOOL_DEFAULT ? ", acd pending" + : (acd_state ? "" : ", acd conflict")); + } if (_LOGD_ENABLED()) { - if (options) { + if (l3cd_changed && options) { gs_free const char **keys = NULL; guint nkeys; guint i; @@ -390,58 +807,41 @@ _nm_dhcp_client_notify(NMDhcpClient *self, keys[i], (char *) g_hash_table_lookup(options, keys[i])); } - - if (priv->config.addr_family == AF_INET6) { - gs_free char *event_id = NULL; - - event_id = nm_dhcp_utils_get_dhcp6_event_id(options); - if (event_id) - _LOGT("event-id: \"%s\"", event_id); - } } } - 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); + if (acd_state == NM_OPTION_BOOL_DEFAULT) { + /* ACD is in progress... */ + return; + } - _LOGI("state changed %s%s%s%s", - priv->l3cd ? "new lease" : "no lease", - NM_PRINT_FMT_QUOTED(addr, ", address=", addr, "", "")); + if (!acd_state) { + gs_free_error GError *error = NULL; + + /* We only decline. We don't actually emit to the caller that + * something is wrong (like NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD). + * If we would, NMDevice might decide to tear down the device, when + * we actually should continue trying to get a better lease. There + * is already "ipv4.dhcp-timeout" which will handle the failure if + * we don't get a good lease. */ + if (!_dhcp_client_decline(self, priv->l3cd_next, "acd failed", &error)) + _LOGD("decline failed: %s", error->message); + return; } - /* 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 _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 (client_event_type == NM_DHCP_CLIENT_EVENT_TYPE_BOUND && priv->l3cd - && nm_l3_config_data_get_num_addresses(priv->l3cd, priv->config.addr_family) > 0) { + nm_l3_config_data_reset(&priv->l3cd_curr, priv->l3cd_next); + + if (client_event_type == NM_DHCP_CLIENT_EVENT_TYPE_BOUND && priv->l3cd_curr + && nm_l3_config_data_get_num_addresses(priv->l3cd_curr, priv->config.addr_family) > 0) priv->l3cfg_notify.wait_dhcp_commit = TRUE; - } else { + else priv->l3cfg_notify.wait_dhcp_commit = FALSE; - } - if (!priv->l3cfg_notify.wait_dhcp_commit && priv->l3cd) { + if (!priv->l3cfg_notify.wait_dhcp_commit && priv->l3cd_curr) { gs_free_error GError *error = NULL; _LOGD("accept lease right away"); - if (!_dhcp_client_accept(self, priv->l3cd, &error)) { + if (!_dhcp_client_accept(self, priv->l3cd_curr, &error)) { _LOGD("accept failed: %s", error->message); /* Unclear why this happened, or what to do about it. Just proceed. */ } @@ -454,7 +854,7 @@ _nm_dhcp_client_notify(NMDhcpClient *self, .notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE, .lease_update = { - .l3cd = priv->l3cd, + .l3cd = priv->l3cd_curr, .accepted = !priv->l3cfg_notify.wait_dhcp_commit, }, }; @@ -529,7 +929,7 @@ _dhcp_client_accept(NMDhcpClient *self, const NML3ConfigData *l3cd, GError **err klass = NM_DHCP_CLIENT_GET_CLASS(self); - g_return_val_if_fail(NM_DHCP_CLIENT_GET_PRIVATE(self)->l3cd, FALSE); + g_return_val_if_fail(NM_DHCP_CLIENT_GET_PRIVATE(self)->l3cd_curr, FALSE); return klass->accept(self, l3cd, error); } @@ -569,7 +969,7 @@ _dhcp_client_decline(NMDhcpClient *self, klass = NM_DHCP_CLIENT_GET_CLASS(self); - g_return_val_if_fail(NM_DHCP_CLIENT_GET_PRIVATE(self)->l3cd, FALSE); + g_return_val_if_fail(NM_DHCP_CLIENT_GET_PRIVATE(self)->l3cd_next, FALSE); return klass->decline(self, l3cd, error_message, error); } @@ -626,7 +1026,9 @@ ipv6_lladdr_find(NMDhcpClient *self) nm_assert(!NM_IS_IPv4(priv->config.addr_family)); l3cfg = priv->config.l3cfg; - nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, nm_l3cfg_get_ifindex(l3cfg)); + nmp_lookup_init_object_by_ifindex(&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); @@ -651,7 +1053,7 @@ ipv6_tentative_addr_find(NMDhcpClient *self) /* For each address in the lease, check whether it's tentative * in platform. */ - nm_l3_config_data_iter_ip6_address_for_each (&iter, priv->l3cd, &addr) { + nm_l3_config_data_iter_ip6_address_for_each (&iter, priv->l3cd_curr, &addr) { const NMPlatformIP6Address *pladdr; NMPObject needle; @@ -676,6 +1078,7 @@ static void l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcpClient *self) { NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; nm_assert(l3cfg == priv->config.l3cfg); @@ -717,7 +1120,7 @@ l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcp self, &((NMDhcpClientNotifyData){.notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE, .lease_update = { - .l3cd = priv->l3cd, + .l3cd = priv->l3cd_curr, .accepted = TRUE, }})); } @@ -736,7 +1139,7 @@ l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcp * lease and notifying NMDevice. */ nm_l3_config_data_iter_ip_address_for_each (&ipconf_iter, - priv->l3cd, + priv->l3cd_curr, priv->config.addr_family, &lease_address) break; @@ -778,9 +1181,11 @@ l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcp _LOGD("accept lease"); - if (!_dhcp_client_accept(self, priv->l3cd, &error)) { + if (!_dhcp_client_accept(self, priv->l3cd_curr, &error)) { gs_free char *reason = g_strdup_printf("error accepting lease: %s", error->message); + _LOGD("accept failed: %s", error->message); + _emit_notify(self, &((NMDhcpClientNotifyData){ .notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD, @@ -794,12 +1199,54 @@ l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcp self, &((NMDhcpClientNotifyData){.notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE, .lease_update = { - .l3cd = priv->l3cd, + .l3cd = priv->l3cd_curr, .accepted = TRUE, }})); } } -wait_dhcp_commit_done:; +wait_dhcp_commit_done: + + if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT + && priv->v4.acd.l3cfg_commit_handle) { + nm_assert(priv->v4.acd.addr != INADDR_ANY); + nm_assert(priv->v4.acd.state == NM_OPTION_BOOL_DEFAULT); + nm_assert(!priv->v4.acd.done_source); + + if (priv->v4.acd.addr == notify_data->acd_event.info.addr + && nm_l3_acd_addr_info_find_track_info(¬ify_data->acd_event.info, + L3CD_ACD_TAG(priv), + NULL, + NULL)) { + NMOptionBool acd_state; + + switch (notify_data->acd_event.info.state) { + default: + nm_assert_not_reached(); + /* fall-through */ + case NM_L3_ACD_ADDR_STATE_INIT: + case NM_L3_ACD_ADDR_STATE_PROBING: + acd_state = NM_OPTION_BOOL_DEFAULT; + break; + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + acd_state = NM_OPTION_BOOL_FALSE; + break; + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_DEFENDING: + acd_state = NM_OPTION_BOOL_TRUE; + break; + } + if (acd_state != NM_OPTION_BOOL_DEFAULT) { + _LOGD("acd: acd %s for %s", + acd_state ? "ready" : "conflict", + _nm_utils_inet4_ntop(priv->v4.acd.addr, sbuf_addr)); + nm_l3cfg_commit_type_clear(priv->config.l3cfg, &priv->v4.acd.l3cfg_commit_handle); + priv->v4.acd.state = acd_state; + priv->v4.acd.done_source = nm_g_idle_add_source(_acd_complete_on_idle_cb, self); + } + } + } } gboolean @@ -928,6 +1375,8 @@ nm_dhcp_client_stop(NMDhcpClient *self, gboolean release) "dhcp stopping"); } + _acd_state_reset(self, TRUE, TRUE); + priv->l3cfg_notify.wait_dhcp_commit = FALSE; priv->l3cfg_notify.wait_ll_address = FALSE; priv->l3cfg_notify.wait_ipv6_dad = FALSE; @@ -942,6 +1391,9 @@ nm_dhcp_client_stop(NMDhcpClient *self, gboolean release) _LOGI("canceled DHCP transaction"); nm_assert(priv->pid == -1); + nm_clear_l3cd(&priv->l3cd_next); + nm_clear_l3cd(&priv->l3cd_curr); + _nm_dhcp_client_notify(self, NM_DHCP_CLIENT_EVENT_TYPE_TERMINATED, NULL); } @@ -951,37 +1403,31 @@ static char * bytearray_variant_to_string(NMDhcpClient *self, GVariant *value, const char *key) { const guint8 *array; + char *str; gsize length; - GString *str; - int i; - unsigned char c; - char *converted = NULL; + gsize i; - g_return_val_if_fail(value != NULL, NULL); + nm_assert(value); array = g_variant_get_fixed_array(value, &length, 1); - /* Since the DHCP options come through environment variables, they should - * already be UTF-8 safe, but just make sure. + /* Since the DHCP options come originally came as environment variables, they + * have not guaranteed encoding. Let's only accept ASCII here. */ - str = g_string_sized_new(length); + str = g_malloc(length + 1); for (i = 0; i < length; i++) { - c = array[i]; + guint8 c = array[i]; - /* Convert NULLs to spaces and non-ASCII characters to ? */ if (c == '\0') - c = ' '; + str[i] = ' '; else if (c > 127) - c = '?'; - str = g_string_append_c(str, c); + str[i] = '?'; + else + str[i] = (char) c; } - str = g_string_append_c(str, '\0'); + str[i] = '\0'; - converted = str->str; - if (!g_utf8_validate(converted, -1, NULL)) - _LOGW("option '%s' couldn't be converted to UTF-8", key); - g_string_free(str, FALSE); - return converted; + return str; } static int @@ -1004,11 +1450,13 @@ label_is_unknown_xyz(const char *label) static void maybe_add_option(NMDhcpClient *self, GHashTable *hash, const char *key, GVariant *value) { - char *str_value = NULL; + char *str_value; + int priv_opt_num; - g_return_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE_BYTESTRING)); + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_BYTESTRING)) + return; - if (g_str_has_prefix(key, OLD_TAG)) + if (NM_STR_HAS_PREFIX(key, OLD_TAG)) return; /* Filter out stuff that's not actually new DHCP options */ @@ -1021,34 +1469,33 @@ maybe_add_option(NMDhcpClient *self, GHashTable *hash, const char *key, GVariant return; str_value = bytearray_variant_to_string(self, value, key); - if (str_value) { - int priv_opt_num; + if (!str_value) + return; - g_hash_table_insert(hash, g_strdup(key), str_value); + g_hash_table_insert(hash, g_strdup(key), str_value); - /* dhclient has no special labels for private dhcp options: it uses "unknown_xyz" + /* 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; + 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: + /* 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); - } + 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); } } @@ -1079,8 +1526,9 @@ nm_dhcp_client_handle_event(gpointer unused, nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; NMDhcpClientEventType client_event_type; NMPlatformIP6Address prefix = { - 0, + 0, }; + int IS_IPv4; g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE); g_return_val_if_fail(iface != NULL, FALSE); @@ -1091,6 +1539,8 @@ nm_dhcp_client_handle_event(gpointer unused, priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + g_return_val_if_fail(!priv->is_stopped, FALSE); + if (!nm_streq0(priv->config.iface, iface)) return FALSE; if (priv->pid != pid) @@ -1175,10 +1625,12 @@ nm_dhcp_client_handle_event(gpointer unused, client_event_type = NM_DHCP_CLIENT_EVENT_TYPE_FAIL; } - if (priv->v4.bound.invocation) + IS_IPv4 = NM_IS_IPv4(priv->config.addr_family); + + if (IS_IPv4 && priv->v4.bound.invocation) g_dbus_method_invocation_return_value(g_steal_pointer(&priv->v4.bound.invocation), NULL); - if (NM_IS_IPv4(priv->config.addr_family) + if (IS_IPv4 && NM_IN_SET(client_event_type, NM_DHCP_CLIENT_EVENT_TYPE_BOUND, NM_DHCP_CLIENT_EVENT_TYPE_EXTENDED)) @@ -1202,23 +1654,23 @@ nm_dhcp_client_server_id_is_rejected(NMDhcpClient *self, gconstpointer addr) /* 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; + if (priv->config.reject_servers) { + 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; @@ -1251,7 +1703,7 @@ config_init(NMDhcpClientConfig *config, const NMDhcpClientConfig *src) 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); + config->reject_servers = nm_strv_dup_packed(config->reject_servers, -1); if (NM_IS_IPv4(config->addr_family)) config->v4.last_address = g_strdup(config->v4.last_address); @@ -1284,7 +1736,7 @@ config_init(NMDhcpClientConfig *config, const NMDhcpClientConfig *src) 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_sd_hostname_is_valid(config->hostname, FALSE))) { + || (!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), @@ -1310,8 +1762,7 @@ config_clear(NMDhcpClientConfig *config) 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); + nm_clear_g_free((gpointer *) &config->reject_servers); if (config->addr_family == AF_INET) { nm_clear_g_free((gpointer *) &config->v4.last_address); @@ -1338,6 +1789,13 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps { .invocation = NULL, }, + .acd = + { + .addr = INADDR_ANY, + .state = NM_OPTION_BOOL_DEFAULT, + .l3cfg_commit_handle = NULL, + .done_source = NULL, + }, }; } else { priv->v6 = (typeof(priv->v6)){ @@ -1383,6 +1841,11 @@ dispose(GObject *object) nm_clear_pointer(&priv->effective_client_id, g_bytes_unref); + nm_assert(!priv->watch_source); + nm_assert(!priv->l3cd_next); + nm_assert(!priv->l3cd_curr); + nm_assert(priv->l3cfg_notify.id == 0); + G_OBJECT_CLASS(nm_dhcp_client_parent_class)->dispose(object); } -- cgit 1.3.0-6-gf8a5