NetworkManager/src/core/dhcp/nm-dhcp-systemd.c
Thomas Haller c020f618ed
dhcp: add and use nm_dhcp_client_create_options_dict()
This will be used to pre-fill the lease with client-specific options.
2022-12-19 11:29:13 +01:00

454 lines
15 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2014 Red Hat, Inc.
*/
#include "src/core/nm-default-daemon.h"
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <net/if_arp.h>
#include "libnm-glib-aux/nm-dedup-multi.h"
#include "libnm-std-aux/unaligned.h"
#include "nm-utils.h"
#include "nm-l3-config-data.h"
#include "nm-dhcp-utils.h"
#include "nm-dhcp-options.h"
#include "nm-core-utils.h"
#include "NetworkManagerUtils.h"
#include "libnm-platform/nm-platform.h"
#include "nm-dhcp-client-logging.h"
#include "libnm-systemd-core/nm-sd.h"
/*****************************************************************************/
#define NM_TYPE_DHCP_SYSTEMD (nm_dhcp_systemd_get_type())
#define NM_DHCP_SYSTEMD(obj) \
(_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_SYSTEMD, NMDhcpSystemd))
#define NM_DHCP_SYSTEMD_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_SYSTEMD, NMDhcpSystemdClass))
#define NM_IS_DHCP_SYSTEMD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_SYSTEMD))
#define NM_IS_DHCP_SYSTEMD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_SYSTEMD))
#define NM_DHCP_SYSTEMD_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_SYSTEMD, NMDhcpSystemdClass))
typedef struct _NMDhcpSystemd NMDhcpSystemd;
typedef struct _NMDhcpSystemdClass NMDhcpSystemdClass;
static GType nm_dhcp_systemd_get_type(void);
/*****************************************************************************/
typedef struct {
sd_dhcp6_client *client6;
char *lease_file;
guint request_count;
} NMDhcpSystemdPrivate;
struct _NMDhcpSystemd {
NMDhcpClient parent;
NMDhcpSystemdPrivate _priv;
};
struct _NMDhcpSystemdClass {
NMDhcpClientClass parent;
};
G_DEFINE_TYPE(NMDhcpSystemd, nm_dhcp_systemd, NM_TYPE_DHCP_CLIENT)
#define NM_DHCP_SYSTEMD_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDhcpSystemd, NM_IS_DHCP_SYSTEMD)
/*****************************************************************************/
static NML3ConfigData *
lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GError **error)
{
nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
gs_unref_hashtable GHashTable *options = NULL;
struct in6_addr tmp_addr;
const struct in6_addr *dns;
char addr_str[NM_INET_ADDRSTRLEN];
char **domains;
char **ntp_fqdns;
const struct in6_addr *ntp_addrs;
const char *s;
nm_auto_free_gstring GString *str = NULL;
int num, i;
nm_assert(lease);
l3cd = nm_dhcp_client_create_l3cd(NM_DHCP_CLIENT(self));
options = nm_dhcp_client_create_options_dict(NM_DHCP_CLIENT(self), TRUE);
if (!nm_dhcp_client_get_config(NM_DHCP_CLIENT(self))->v6.info_only) {
gboolean has_any_addresses = FALSE;
uint32_t lft_pref;
uint32_t lft_valid;
sd_dhcp6_lease_reset_address_iter(lease);
nm_gstring_prepare(&str);
while (sd_dhcp6_lease_get_address(lease, &tmp_addr, &lft_pref, &lft_valid) >= 0) {
const NMPlatformIP6Address address = {
.plen = 128,
.address = tmp_addr,
.timestamp = ts,
.lifetime = lft_valid,
.preferred = lft_pref,
.addr_source = NM_IP_CONFIG_SOURCE_DHCP,
};
nm_l3_config_data_add_address_6(l3cd, &address);
nm_inet6_ntop(&tmp_addr, addr_str);
g_string_append(nm_gstring_add_space_delimiter(str), addr_str);
has_any_addresses = TRUE;
}
if (str->len) {
nm_dhcp_option_add_option(options,
AF_INET6,
NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS,
str->str);
}
if (!has_any_addresses) {
g_set_error_literal(error,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_FAILED,
"no address received in managed mode");
return NULL;
}
}
num = sd_dhcp6_lease_get_dns(lease, &dns);
if (num > 0) {
nm_gstring_prepare(&str);
for (i = 0; i < num; i++) {
nm_inet6_ntop(&dns[i], addr_str);
g_string_append(nm_gstring_add_space_delimiter(str), addr_str);
nm_l3_config_data_add_nameserver_detail(l3cd, AF_INET6, &dns[i], NULL);
}
nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_DNS_SERVERS, str->str);
}
num = sd_dhcp6_lease_get_domains(lease, &domains);
if (num > 0) {
nm_gstring_prepare(&str);
for (i = 0; i < num; i++) {
g_string_append(nm_gstring_add_space_delimiter(str), domains[i]);
nm_l3_config_data_add_search(l3cd, AF_INET6, domains[i]);
}
nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_DOMAIN_LIST, str->str);
}
if (sd_dhcp6_lease_get_fqdn(lease, &s) >= 0) {
nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_FQDN, s);
}
/* RFC 5908, section 4 states: "This option MUST include one, and only
* one, time source suboption." It is not clear why systemd chose to
* return array of addresses and FQDNs. Given there seem to be no
* technical obstacles to including multiple options, let's just
* pass on whatever systemd tells us.
*/
nm_gstring_prepare(&str);
num = sd_dhcp6_lease_get_ntp_fqdn(lease, &ntp_fqdns);
if (num > 0) {
for (i = 0; i < num; i++) {
g_string_append(nm_gstring_add_space_delimiter(str), ntp_fqdns[i]);
}
}
num = sd_dhcp6_lease_get_ntp_addrs(lease, &ntp_addrs);
if (num > 0) {
for (i = 0; i < num; i++) {
nm_inet6_ntop(&ntp_addrs[i], addr_str);
g_string_append(nm_gstring_add_space_delimiter(str), addr_str);
}
}
if (str->len) {
nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_NTP_SERVER, str->str);
}
nm_l3_config_data_set_dhcp_lease_from_options(l3cd, AF_INET6, g_steal_pointer(&options));
return g_steal_pointer(&l3cd);
}
static void
bound6_handle(NMDhcpSystemd *self)
{
NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE(self);
const gint32 ts = nm_utils_get_monotonic_timestamp_sec();
nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
gs_free_error GError *error = NULL;
NMPlatformIP6Address prefix = {0};
sd_dhcp6_lease *lease = NULL;
if (sd_dhcp6_client_get_lease(priv->client6, &lease) < 0 || !lease) {
_LOGW(" no lease!");
_nm_dhcp_client_notify(NM_DHCP_CLIENT(self), NM_DHCP_CLIENT_EVENT_TYPE_FAIL, NULL);
return;
}
_LOGD("lease available");
l3cd = lease_to_ip6_config(self, lease, ts, &error);
if (!l3cd) {
_LOGW("%s", error->message);
_nm_dhcp_client_notify(NM_DHCP_CLIENT(self), NM_DHCP_CLIENT_EVENT_TYPE_FAIL, NULL);
return;
}
_nm_dhcp_client_notify(NM_DHCP_CLIENT(self), NM_DHCP_CLIENT_EVENT_TYPE_BOUND, l3cd);
sd_dhcp6_lease_reset_pd_prefix_iter(lease);
while (!sd_dhcp6_lease_get_pd(lease,
&prefix.address,
&prefix.plen,
&prefix.preferred,
&prefix.lifetime)) {
prefix.timestamp = ts;
nm_dhcp_client_emit_ipv6_prefix_delegated(NM_DHCP_CLIENT(self), &prefix);
}
}
static void
dhcp6_event_cb(sd_dhcp6_client *client, int event, gpointer user_data)
{
NMDhcpSystemd *self = NM_DHCP_SYSTEMD(user_data);
NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE(self);
nm_assert(priv->client6 == client);
_LOGD("client event %d", event);
switch (event) {
case SD_DHCP6_CLIENT_EVENT_RETRANS_MAX:
_nm_dhcp_client_notify(NM_DHCP_CLIENT(user_data), NM_DHCP_CLIENT_EVENT_TYPE_TIMEOUT, NULL);
break;
case SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE:
case SD_DHCP6_CLIENT_EVENT_STOP:
_nm_dhcp_client_notify(NM_DHCP_CLIENT(user_data), NM_DHCP_CLIENT_EVENT_TYPE_FAIL, NULL);
break;
case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE:
case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST:
bound6_handle(self);
break;
default:
_LOGW("unhandled event %d", event);
break;
}
}
static gboolean
ip6_start(NMDhcpClient *client, const struct in6_addr *ll_addr, GError **error)
{
NMDhcpSystemd *self = NM_DHCP_SYSTEMD(client);
NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE(self);
nm_auto(sd_dhcp6_client_unrefp) sd_dhcp6_client *sd_client = NULL;
const NMDhcpClientConfig *client_config;
const char *hostname;
const char *mud_url;
int r, i;
const guint8 *duid_arr;
gsize duid_len;
GBytes *duid;
gboolean prefix_delegation;
g_return_val_if_fail(!priv->client6, FALSE);
client_config = nm_dhcp_client_get_config(client);
/* TODO: honor nm_dhcp_client_get_anycast_address() */
duid = client_config->client_id;
if (!duid || !(duid_arr = g_bytes_get_data(duid, &duid_len)) || duid_len < 2) {
nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "missing DUID");
g_return_val_if_reached(FALSE);
}
r = sd_dhcp6_client_new(&sd_client);
if (r < 0) {
nm_utils_error_set_errno(error, r, "failed to create dhcp-client: %s");
return FALSE;
}
_LOGT("dhcp-client6: set %p", sd_client);
sd_dhcp6_client_set_address_request(sd_client, !client_config->v6.info_only);
sd_dhcp6_client_set_information_request(sd_client,
client_config->v6.info_only
&& client_config->v6.needed_prefixes == 0);
r = sd_dhcp6_client_set_iaid(sd_client, client_config->v6.iaid);
if (r < 0) {
nm_utils_error_set_errno(error, r, "failed to set IAID: %s");
return FALSE;
}
r = sd_dhcp6_client_set_duid(sd_client,
unaligned_read_be16(&duid_arr[0]),
&duid_arr[2],
duid_len - 2);
if (r < 0) {
nm_utils_error_set_errno(error, r, "failed to set DUID: %s");
return FALSE;
}
r = sd_dhcp6_client_attach_event(sd_client, NULL, 0);
if (r < 0) {
nm_utils_error_set_errno(error, r, "failed to attach event: %s");
return FALSE;
}
r = sd_dhcp6_client_set_ifindex(sd_client, nm_dhcp_client_get_ifindex(client));
if (r < 0) {
nm_utils_error_set_errno(error, r, "failed to set ifindex: %s");
return FALSE;
}
/* Add requested options */
for (i = 0; i < (int) G_N_ELEMENTS(_nm_dhcp_option_dhcp6_options); i++) {
if (_nm_dhcp_option_dhcp6_options[i].include) {
r = sd_dhcp6_client_set_request_option(sd_client,
_nm_dhcp_option_dhcp6_options[i].option_num);
nm_assert(r >= 0 || r == -EEXIST);
}
}
mud_url = client_config->mud_url;
if (mud_url) {
r = sd_dhcp6_client_set_request_mud_url(sd_client, mud_url);
if (r < 0) {
nm_utils_error_set_errno(error, r, "failed to set mud-url: %s");
return FALSE;
}
}
prefix_delegation = FALSE;
if (client_config->v6.needed_prefixes > 0) {
if (client_config->v6.needed_prefixes > 1) {
/* FIXME: systemd-networkd API only allows to request a
* single prefix */
_LOGW("dhcp-client6: only one prefix request is supported");
}
prefix_delegation = TRUE;
}
r = sd_dhcp6_client_set_prefix_delegation(sd_client, prefix_delegation);
if (r < 0) {
nm_utils_error_set_errno(error, r, "failed to enable prefix delegation: %s");
return FALSE;
}
r = sd_dhcp6_client_set_local_address(sd_client, ll_addr);
if (r < 0) {
nm_utils_error_set_errno(error, r, "failed to set local address: %s");
return FALSE;
}
hostname = client_config->hostname;
r = sd_dhcp6_client_set_fqdn(sd_client, hostname);
if (r < 0) {
nm_utils_error_set_errno(error, r, "failed to set DHCP hostname: %s");
return FALSE;
}
r = sd_dhcp6_client_set_callback(sd_client, dhcp6_event_cb, client);
if (r < 0) {
nm_utils_error_set_errno(error, r, "failed to set callback: %s");
return FALSE;
}
priv->client6 = g_steal_pointer(&sd_client);
r = sd_dhcp6_client_start(priv->client6);
if (r < 0) {
sd_dhcp6_client_set_callback(priv->client6, NULL, NULL);
nm_clear_pointer(&priv->client6, sd_dhcp6_client_unref);
nm_utils_error_set_errno(error, r, "failed to start client: %s");
return FALSE;
}
nm_dhcp_client_set_effective_client_id(client, duid);
return TRUE;
}
static void
stop(NMDhcpClient *client, gboolean release)
{
NMDhcpSystemd *self = NM_DHCP_SYSTEMD(client);
NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE(self);
int r = 0;
NM_DHCP_CLIENT_CLASS(nm_dhcp_systemd_parent_class)->stop(client, release);
_LOGT("dhcp-client6: stop");
if (!priv->client6)
return;
sd_dhcp6_client_set_callback(priv->client6, NULL, NULL);
r = sd_dhcp6_client_stop(priv->client6);
if (r)
_LOGW("failed to stop client (%d)", r);
}
/*****************************************************************************/
static void
nm_dhcp_systemd_init(NMDhcpSystemd *self)
{}
static void
dispose(GObject *object)
{
NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE(object);
nm_clear_g_free(&priv->lease_file);
if (priv->client6) {
sd_dhcp6_client_stop(priv->client6);
sd_dhcp6_client_unref(priv->client6);
priv->client6 = NULL;
}
G_OBJECT_CLASS(nm_dhcp_systemd_parent_class)->dispose(object);
}
static void
nm_dhcp_systemd_class_init(NMDhcpSystemdClass *sdhcp_class)
{
NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS(sdhcp_class);
GObjectClass *object_class = G_OBJECT_CLASS(sdhcp_class);
object_class->dispose = dispose;
client_class->ip6_start = ip6_start;
client_class->stop = stop;
}
const NMDhcpClientFactory _nm_dhcp_client_factory_systemd = {
.name = "systemd",
.get_type_4 = nm_dhcp_nettools_get_type,
.get_type_6 = nm_dhcp_systemd_get_type,
.undocumented = TRUE,
};
/*****************************************************************************/
const NMDhcpClientFactory _nm_dhcp_client_factory_internal = {
.name = "internal",
.get_type_4 = nm_dhcp_nettools_get_type,
.get_type_6 = nm_dhcp_systemd_get_type,
};