diff options
| author | Sebastien Bacher <seb128@ubuntu.com> | 2021-02-25 11:35:01 +0100 |
|---|---|---|
| committer | Sebastien Bacher <seb128@ubuntu.com> | 2021-02-25 11:49:48 +0100 |
| commit | 77dea84aa6e816f66dc622cec8ae2668eba3c068 (patch) | |
| tree | 755536a0ed1fb36c2899e9bff9e092c4008a25c4 /src/core/dhcp/nm-dhcp-manager.c | |
| parent | 47f08826b44eed651b44dc8bacb5b46bc4c52067 (diff) | |
| parent | fd80297a396cc4dd7da4da20208fb360aa369aa7 (diff) | |
Merge branch 'debian/master' into ubuntu/master
Diffstat (limited to 'src/core/dhcp/nm-dhcp-manager.c')
| -rw-r--r-- | src/core/dhcp/nm-dhcp-manager.c | 682 |
1 files changed, 682 insertions, 0 deletions
diff --git a/src/core/dhcp/nm-dhcp-manager.c b/src/core/dhcp/nm-dhcp-manager.c new file mode 100644 index 00000000..bc114ad8 --- /dev/null +++ b/src/core/dhcp/nm-dhcp-manager.c @@ -0,0 +1,682 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2013 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-dhcp-manager.h" + +#include <sys/socket.h> +#include <sys/wait.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> + +#include "nm-glib-aux/nm-dedup-multi.h" +#include "systemd/nm-sd-utils-shared.h" + +#include "nm-config.h" +#include "NetworkManagerUtils.h" + +/*****************************************************************************/ + +typedef struct { + const NMDhcpClientFactory *client_factory; + char * default_hostname; + CList dhcp_client_lst_head; +} NMDhcpManagerPrivate; + +struct _NMDhcpManager { + GObject parent; + NMDhcpManagerPrivate _priv; +}; + +struct _NMDhcpManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMDhcpManager, nm_dhcp_manager, G_TYPE_OBJECT) + +#define NM_DHCP_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDhcpManager, NM_IS_DHCP_MANAGER) + +/*****************************************************************************/ + +static void client_state_changed(NMDhcpClient * client, + NMDhcpState state, + GObject * ip_config, + GVariant * options, + NMDhcpManager *self); + +/*****************************************************************************/ + +/* default to installed helper, but can be modified for testing */ +const char *nm_dhcp_helper_path = LIBEXECDIR "/nm-dhcp-helper"; + +/*****************************************************************************/ + +static const NMDhcpClientFactory * +_client_factory_find_by_name(const char *name) +{ + int i; + + g_return_val_if_fail(name, NULL); + + for (i = 0; i < G_N_ELEMENTS(_nm_dhcp_manager_factories); i++) { + const NMDhcpClientFactory *f = _nm_dhcp_manager_factories[i]; + + if (f && nm_streq(f->name, name)) + return f; + } + return NULL; +} + +static const NMDhcpClientFactory * +_client_factory_available(const NMDhcpClientFactory *client_factory) +{ + if (client_factory && (!client_factory->get_path || client_factory->get_path())) + return client_factory; + return NULL; +} + +static GType +_client_factory_get_gtype(const NMDhcpClientFactory *client_factory, int addr_family) +{ + GType gtype; + nm_auto_unref_gtypeclass NMDhcpClientClass *klass = NULL; + + nm_assert(client_factory); + nm_assert_addr_family(addr_family); + + /* currently, the chosen DHCP plugin for IPv4 and IPv6 is configured in NetworkManager.conf + * and cannot be reloaded. It would be nice to configure the plugin per address family + * or to be able to reload it. + * + * Note that certain options in NetworkManager.conf depend on the chosen DHCP plugin. + * See "dhcp-plugin:" in "Device List Format" (`man NetworkManager.conf`). + * Supporting reloading the plugin would also require to re-evalate the decisions from + * the "Device List Format". Likewise, having per-address family plugins would make the + * "main.dhcp" setting and "dhcp-plugin:" match non-sensical because these configurations + * currently are address family independent. + * + * So actually, we don't want that complexity. We want to phase out all plugins in favor + * of the internal plugin. + * However, certain existing plugins are well known to not support an address family. + * In those cases, we should just silently fallback to the internal plugin. + * + * This could be a problem with forward compatibility if we ever intended to add IPv6 support + * to those plugins. But we don't intend to do so. The internal plugin is the way forward and + * not extending other plugins. */ + + if (client_factory->get_type_per_addr_family) + gtype = client_factory->get_type_per_addr_family(addr_family); + else + gtype = client_factory->get_type(); + + if (client_factory == &_nm_dhcp_client_factory_internal) { + /* we are already using the internal plugin. Nothing to do. */ + goto out; + } + + klass = g_type_class_ref(gtype); + + nm_assert(NM_IS_DHCP_CLIENT_CLASS(klass)); + + if (addr_family == AF_INET6) { + if (!klass->ip6_start) + gtype = _client_factory_get_gtype(&_nm_dhcp_client_factory_internal, addr_family); + } else { + if (!klass->ip4_start) + gtype = _client_factory_get_gtype(&_nm_dhcp_client_factory_internal, addr_family); + } + +out: + nm_assert(g_type_is_a(gtype, NM_TYPE_DHCP_CLIENT)); + nm_assert(({ + nm_auto_unref_gtypeclass NMDhcpClientClass *k = g_type_class_ref(gtype); + + (addr_family == AF_INET6 && k->ip6_start) || (addr_family == AF_INET && k->ip4_start); + })); + + return gtype; +} + +/*****************************************************************************/ + +static NMDhcpClient * +get_client_for_ifindex(NMDhcpManager *manager, int addr_family, int ifindex) +{ + NMDhcpManagerPrivate *priv; + NMDhcpClient * client; + + g_return_val_if_fail(NM_IS_DHCP_MANAGER(manager), NULL); + g_return_val_if_fail(ifindex > 0, NULL); + + priv = NM_DHCP_MANAGER_GET_PRIVATE(manager); + + c_list_for_each_entry (client, &priv->dhcp_client_lst_head, dhcp_client_lst) { + if (nm_dhcp_client_get_ifindex(client) == ifindex + && nm_dhcp_client_get_addr_family(client) == addr_family) + return client; + } + + return NULL; +} + +static void +remove_client(NMDhcpManager *self, NMDhcpClient *client) +{ + g_signal_handlers_disconnect_by_func(client, client_state_changed, self); + c_list_unlink(&client->dhcp_client_lst); + + /* Stopping the client is left up to the controlling device + * explicitly since we may want to quit NetworkManager but not terminate + * the DHCP client. + */ +} + +static void +remove_client_unref(NMDhcpManager *self, NMDhcpClient *client) +{ + remove_client(self, client); + g_object_unref(client); +} + +static void +client_state_changed(NMDhcpClient * client, + NMDhcpState state, + GObject * ip_config, + GVariant * options, + NMDhcpManager *self) +{ + if (state >= NM_DHCP_STATE_TIMEOUT) + remove_client_unref(self, client); +} + +static NMDhcpClient * +client_start(NMDhcpManager * self, + int addr_family, + NMDedupMultiIndex * multi_idx, + const char * iface, + int ifindex, + GBytes * hwaddr, + GBytes * bcast_hwaddr, + const char * uuid, + guint32 route_table, + guint32 route_metric, + const struct in6_addr * ipv6_ll_addr, + GBytes * dhcp_client_id, + gboolean enforce_duid, + guint32 iaid, + gboolean iaid_explicit, + guint32 timeout, + const char * dhcp_anycast_addr, + const char * hostname, + gboolean hostname_use_fqdn, + NMDhcpHostnameFlags hostname_flags, + const char * mud_url, + gboolean info_only, + NMSettingIP6ConfigPrivacy privacy, + const char * last_ip4_address, + guint needed_prefixes, + GBytes * vendor_class_identifier, + const char *const * reject_servers, + GError ** error) +{ + NMDhcpManagerPrivate *priv; + NMDhcpClient * client; + gboolean success = FALSE; + gsize hwaddr_len; + GType gtype; + + g_return_val_if_fail(NM_IS_DHCP_MANAGER(self), NULL); + g_return_val_if_fail(iface, NULL); + g_return_val_if_fail(ifindex > 0, NULL); + g_return_val_if_fail(uuid != NULL, NULL); + g_return_val_if_fail(!dhcp_client_id || g_bytes_get_size(dhcp_client_id) >= 2, NULL); + g_return_val_if_fail(!vendor_class_identifier + || g_bytes_get_size(vendor_class_identifier) <= 255, + NULL); + g_return_val_if_fail(!error || !*error, NULL); + + if (addr_family == AF_INET) { + if (!hwaddr || !bcast_hwaddr) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "missing %s address", + hwaddr ? "broadcast" : "MAC"); + return NULL; + } + + hwaddr_len = g_bytes_get_size(hwaddr); + if (hwaddr_len == 0 || hwaddr_len > NM_UTILS_HWADDR_LEN_MAX) { + nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "invalid MAC address"); + g_return_val_if_reached(NULL); + } + nm_assert(g_bytes_get_size(hwaddr) == g_bytes_get_size(bcast_hwaddr)); + } else { + hwaddr = NULL; + bcast_hwaddr = NULL; + } + + if (hostname) { + if ((hostname_use_fqdn && !nm_sd_dns_name_is_valid(hostname)) + || (!hostname_use_fqdn && !nm_sd_hostname_is_valid(hostname, FALSE))) { + nm_log_warn(LOGD_DHCP, + "dhcp%c: %s '%s' is invalid, will be ignored", + nm_utils_addr_family_to_char(addr_family), + hostname_use_fqdn ? "FQDN" : "hostname", + hostname); + hostname = NULL; + } + } + + priv = NM_DHCP_MANAGER_GET_PRIVATE(self); + + /* Kill any old client instance */ + client = get_client_for_ifindex(self, addr_family, ifindex); + if (client) { + /* FIXME: we cannot just call synchronously "stop()" and forget about the client. + * We need to wait for the client to be fully stopped because most/all clients + * cannot quit right away. + * + * FIXME(shutdown): also fix this during shutdown, to wait for all DHCP clients + * to be fully stopped. */ + remove_client(self, client); + nm_dhcp_client_stop(client, FALSE); + g_object_unref(client); + } + + gtype = _client_factory_get_gtype(priv->client_factory, addr_family); + + nm_log_trace(LOGD_DHCP, + "dhcp%c: creating IPv%c DHCP client of type %s", + nm_utils_addr_family_to_char(addr_family), + nm_utils_addr_family_to_char(addr_family), + g_type_name(gtype)); + + client = g_object_new(gtype, + NM_DHCP_CLIENT_MULTI_IDX, + multi_idx, + NM_DHCP_CLIENT_ADDR_FAMILY, + addr_family, + NM_DHCP_CLIENT_INTERFACE, + iface, + NM_DHCP_CLIENT_IFINDEX, + ifindex, + NM_DHCP_CLIENT_HWADDR, + hwaddr, + NM_DHCP_CLIENT_BROADCAST_HWADDR, + bcast_hwaddr, + NM_DHCP_CLIENT_UUID, + uuid, + NM_DHCP_CLIENT_IAID, + (guint) iaid, + NM_DHCP_CLIENT_IAID_EXPLICIT, + iaid_explicit, + NM_DHCP_CLIENT_HOSTNAME, + hostname, + NM_DHCP_CLIENT_MUD_URL, + mud_url, + NM_DHCP_CLIENT_ROUTE_TABLE, + (guint) route_table, + NM_DHCP_CLIENT_ROUTE_METRIC, + (guint) route_metric, + NM_DHCP_CLIENT_TIMEOUT, + (guint) timeout, + NM_DHCP_CLIENT_HOSTNAME_FLAGS, + (guint) hostname_flags, + NM_DHCP_CLIENT_VENDOR_CLASS_IDENTIFIER, + vendor_class_identifier, + NM_DHCP_CLIENT_REJECT_SERVERS, + reject_servers, + NM_DHCP_CLIENT_FLAGS, + (guint)(0 | (hostname_use_fqdn ? NM_DHCP_CLIENT_FLAGS_USE_FQDN : 0) + | (info_only ? NM_DHCP_CLIENT_FLAGS_INFO_ONLY : 0)), + NULL); + nm_assert(client && c_list_is_empty(&client->dhcp_client_lst)); + c_list_link_tail(&priv->dhcp_client_lst_head, &client->dhcp_client_lst); + g_signal_connect(client, + NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, + G_CALLBACK(client_state_changed), + self); + + /* unfortunately, our implementations work differently per address-family regarding client-id/DUID. + * + * - for IPv4, the calling code may determine a client-id (from NM's connection profile). + * If present, it is taken. If not present, the DHCP plugin uses a plugin specific default. + * - for "internal" plugin, the default is just "mac". + * - for "dhclient", we try to get the configuration from dhclient's /etc/dhcp or fallback + * to whatever dhclient uses by default. + * We do it this way, because for dhclient the user may configure a default + * outside of NM, and we want to honor that. Worse, dhclient could be a wapper + * script where the wrapper script overwrites the client-id. We need to distinguish + * between: force a particular client-id and leave it unspecified to whatever dhclient + * wants. + * + * - for IPv6, the calling code always determines a client-id. It also specifies @enforce_duid, + * to determine whether the given client-id must be used. + * - for "internal" plugin @enforce_duid doesn't matter and the given client-id is + * always used. + * - for "dhclient", @enforce_duid FALSE means to first try to load the DUID from the + * lease file, and only otherwise fallback to the given client-id. + * - other plugins don't support DHCPv6. + * It's done this way, so that existing dhclient setups don't change behavior on upgrade. + * + * This difference is cumbersome and only exists because of "dhclient" which supports hacking the + * default outside of NetworkManager API. + */ + + if (addr_family == AF_INET) { + success = nm_dhcp_client_start_ip4(client, + dhcp_client_id, + dhcp_anycast_addr, + last_ip4_address, + error); + } else { + success = nm_dhcp_client_start_ip6(client, + dhcp_client_id, + enforce_duid, + dhcp_anycast_addr, + ipv6_ll_addr, + privacy, + needed_prefixes, + error); + } + + if (!success) { + remove_client_unref(self, client); + return NULL; + } + + return g_object_ref(client); +} + +/* Caller owns a reference to the NMDhcpClient on return */ +NMDhcpClient * +nm_dhcp_manager_start_ip4(NMDhcpManager * self, + NMDedupMultiIndex * multi_idx, + const char * iface, + int ifindex, + GBytes * hwaddr, + GBytes * bcast_hwaddr, + const char * uuid, + guint32 route_table, + guint32 route_metric, + gboolean send_hostname, + const char * dhcp_hostname, + const char * dhcp_fqdn, + NMDhcpHostnameFlags hostname_flags, + const char * mud_url, + GBytes * dhcp_client_id, + guint32 timeout, + const char * dhcp_anycast_addr, + const char * last_ip_address, + GBytes * vendor_class_identifier, + const char *const * reject_servers, + GError ** error) +{ + NMDhcpManagerPrivate *priv; + const char * hostname = NULL; + gs_free char * hostname_tmp = NULL; + gboolean use_fqdn = FALSE; + char * dot; + + g_return_val_if_fail(NM_IS_DHCP_MANAGER(self), NULL); + priv = NM_DHCP_MANAGER_GET_PRIVATE(self); + + if (send_hostname) { + /* Use, in order of preference: + * 1. FQDN from configuration + * 2. hostname from configuration + * 3. system hostname (only host part) + */ + if (dhcp_fqdn) { + hostname = dhcp_fqdn; + use_fqdn = TRUE; + } else if (dhcp_hostname) + hostname = dhcp_hostname; + else { + hostname = priv->default_hostname; + if (hostname) { + hostname_tmp = g_strdup(hostname); + dot = strchr(hostname_tmp, '.'); + if (dot) + *dot = '\0'; + hostname = hostname_tmp; + } + } + } + + return client_start(self, + AF_INET, + multi_idx, + iface, + ifindex, + hwaddr, + bcast_hwaddr, + uuid, + route_table, + route_metric, + NULL, + dhcp_client_id, + FALSE, + 0, + FALSE, + timeout, + dhcp_anycast_addr, + hostname, + use_fqdn, + hostname_flags, + mud_url, + FALSE, + 0, + last_ip_address, + 0, + vendor_class_identifier, + reject_servers, + error); +} + +/* Caller owns a reference to the NMDhcpClient on return */ +NMDhcpClient * +nm_dhcp_manager_start_ip6(NMDhcpManager * self, + NMDedupMultiIndex * multi_idx, + const char * iface, + int ifindex, + const struct in6_addr * ll_addr, + const char * uuid, + guint32 route_table, + guint32 route_metric, + gboolean send_hostname, + const char * dhcp_hostname, + NMDhcpHostnameFlags hostname_flags, + const char * mud_url, + GBytes * duid, + gboolean enforce_duid, + guint32 iaid, + gboolean iaid_explicit, + guint32 timeout, + const char * dhcp_anycast_addr, + gboolean info_only, + NMSettingIP6ConfigPrivacy privacy, + guint needed_prefixes, + GError ** error) +{ + NMDhcpManagerPrivate *priv; + const char * hostname = NULL; + + g_return_val_if_fail(NM_IS_DHCP_MANAGER(self), NULL); + priv = NM_DHCP_MANAGER_GET_PRIVATE(self); + + if (send_hostname) { + /* Always prefer the explicit dhcp-hostname if given */ + hostname = dhcp_hostname ?: priv->default_hostname; + } + return client_start(self, + AF_INET6, + multi_idx, + iface, + ifindex, + NULL, + NULL, + uuid, + route_table, + route_metric, + ll_addr, + duid, + enforce_duid, + iaid, + iaid_explicit, + timeout, + dhcp_anycast_addr, + hostname, + TRUE, + hostname_flags, + mud_url, + info_only, + privacy, + NULL, + needed_prefixes, + NULL, + NULL, + error); +} + +void +nm_dhcp_manager_set_default_hostname(NMDhcpManager *manager, const char *hostname) +{ + NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE(manager); + + nm_clear_g_free(&priv->default_hostname); + + /* Never send 'localhost'-type names to the DHCP server */ + if (!nm_utils_is_specific_hostname(hostname)) + return; + + priv->default_hostname = g_strdup(hostname); +} + +const char * +nm_dhcp_manager_get_config(NMDhcpManager *self) +{ + const NMDhcpClientFactory *factory; + + g_return_val_if_fail(NM_IS_DHCP_MANAGER(self), NULL); + + factory = NM_DHCP_MANAGER_GET_PRIVATE(self)->client_factory; + return factory ? factory->name : NULL; +} + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_GETTER(NMDhcpManager, nm_dhcp_manager_get, NM_TYPE_DHCP_MANAGER); + +void +nmtst_dhcp_manager_unget(gpointer self) +{ + _nmtst_nm_dhcp_manager_get_reset(self); +} + +static void +nm_dhcp_manager_init(NMDhcpManager *self) +{ + NMDhcpManagerPrivate * priv = NM_DHCP_MANAGER_GET_PRIVATE(self); + NMConfig * config = nm_config_get(); + gs_free char * client_free = NULL; + const char * client; + int i; + const NMDhcpClientFactory *client_factory = NULL; + + c_list_init(&priv->dhcp_client_lst_head); + + for (i = 0; i < G_N_ELEMENTS(_nm_dhcp_manager_factories); i++) { + const NMDhcpClientFactory *f = _nm_dhcp_manager_factories[i]; + + if (!f) + continue; + + nm_log_dbg(LOGD_DHCP, + "dhcp-init: enabled DHCP client '%s'%s%s", + f->name, + _client_factory_available(f) ? "" : " (not available)", + f->experimental ? " (undocumented internal plugin)" : ""); + } + + /* Client-specific setup */ + client_free = + nm_config_data_get_value(nm_config_get_data_orig(config), + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_DHCP, + NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY); + client = client_free; + if (nm_config_get_configure_and_quit(config) == NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED) { + client_factory = &_nm_dhcp_client_factory_internal; + if (client && !nm_streq(client, client_factory->name)) + nm_log_info(LOGD_DHCP, + "dhcp-init: Using internal DHCP client since configure-and-quit is set."); + } else { + if (client) { + client_factory = _client_factory_available(_client_factory_find_by_name(client)); + if (!client_factory) + nm_log_warn(LOGD_DHCP, "dhcp-init: DHCP client '%s' not available", client); + } + if (!client_factory) { + client_factory = _client_factory_find_by_name("" NM_CONFIG_DEFAULT_MAIN_DHCP); + if (!client_factory) + nm_log_err(LOGD_DHCP, + "dhcp-init: default DHCP client '%s' is not installed", + NM_CONFIG_DEFAULT_MAIN_DHCP); + else { + client_factory = _client_factory_available(client_factory); + if (!client_factory) + nm_log_info(LOGD_DHCP, + "dhcp-init: default DHCP client '%s' is not available", + NM_CONFIG_DEFAULT_MAIN_DHCP); + } + } + if (!client_factory) { + for (i = 0; i < G_N_ELEMENTS(_nm_dhcp_manager_factories); i++) { + client_factory = _client_factory_available(_nm_dhcp_manager_factories[i]); + if (client_factory) + break; + } + } + } + + g_return_if_fail(client_factory); + + nm_log_info(LOGD_DHCP, "dhcp-init: Using DHCP client '%s'", client_factory->name); + + /* NOTE: currently the DHCP plugin is chosen once at start. It's not + * possible to reload that configuration. If that ever becomes possible, + * beware that the "dhcp-plugin" device spec made decisions based on + * the previous plugin and may need reevaluation. */ + priv->client_factory = client_factory; +} + +static void +dispose(GObject *object) +{ + NMDhcpManager * self = NM_DHCP_MANAGER(object); + NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE(self); + NMDhcpClient * client, *client_safe; + + c_list_for_each_entry_safe (client, client_safe, &priv->dhcp_client_lst_head, dhcp_client_lst) + remove_client_unref(self, client); + + G_OBJECT_CLASS(nm_dhcp_manager_parent_class)->dispose(object); + + nm_clear_g_free(&priv->default_hostname); +} + +static void +nm_dhcp_manager_class_init(NMDhcpManagerClass *manager_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(manager_class); + + object_class->dispose = dispose; +} |