diff options
| author | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
| commit | e74c568b07b50b97873fb4ee1d776dedefbd54d6 (patch) | |
| tree | 3469f17ea9af91f7ff169b890633bda68b0cf76e /src/libnm-systemd-core | |
| parent | bfe522304da217296e2a61040f58e35ec5d6f3f2 (diff) | |
New upstream version 1.32.12 upstream/1.32.12
Diffstat (limited to 'src/libnm-systemd-core')
69 files changed, 18907 insertions, 0 deletions
diff --git a/src/libnm-systemd-core/meson.build b/src/libnm-systemd-core/meson.build new file mode 100644 index 00000000..7ecf3164 --- /dev/null +++ b/src/libnm-systemd-core/meson.build @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libnm_systemd_core = static_library( + 'nm-systemd-core', + sources: files( + 'src/libsystemd-network/arp-util.c', + 'src/libsystemd-network/dhcp-identifier.c', + 'src/libsystemd-network/dhcp-network.c', + 'src/libsystemd-network/dhcp-option.c', + 'src/libsystemd-network/dhcp-packet.c', + 'src/libsystemd-network/dhcp6-network.c', + 'src/libsystemd-network/dhcp6-option.c', + 'src/libsystemd-network/lldp-neighbor.c', + 'src/libsystemd-network/lldp-network.c', + 'src/libsystemd-network/network-common.c', + 'src/libsystemd-network/network-internal.c', + 'src/libsystemd-network/sd-dhcp-client.c', + 'src/libsystemd-network/sd-dhcp-lease.c', + 'src/libsystemd-network/sd-dhcp6-client.c', + 'src/libsystemd-network/sd-dhcp6-lease.c', + 'src/libsystemd-network/sd-ipv4acd.c', + 'src/libsystemd-network/sd-ipv4ll.c', + 'src/libsystemd-network/sd-lldp.c', + 'src/libsystemd/sd-event/event-util.c', + 'src/libsystemd/sd-event/sd-event.c', + 'src/libsystemd/sd-id128/id128-util.c', + 'src/libsystemd/sd-id128/sd-id128.c', + 'nm-sd.c', + 'nm-sd-utils-core.c', + 'nm-sd-utils-dhcp.c', + 'sd-adapt-core/nm-sd-adapt-core.c', + ), + include_directories: [ + include_directories( + 'sd-adapt-core', + 'src/libsystemd-network', + 'src/libsystemd/sd-event', + 'src/systemd', + ), + top_inc, + src_inc, + ], + dependencies: [ + libnm_systemd_shared_dep_inc, + glib_dep, + ], +) + +libnm_systemd_core_dep_inc = declare_dependency( + include_directories: include_directories( + 'sd-adapt-core', + 'src/libsystemd-network', + 'src/libsystemd/sd-event', + 'src/systemd', + ), +) diff --git a/src/libnm-systemd-core/nm-default-systemd-core.h b/src/libnm-systemd-core/nm-default-systemd-core.h new file mode 100644 index 00000000..07d45568 --- /dev/null +++ b/src/libnm-systemd-core/nm-default-systemd-core.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NM_DEFAULT_SYSTEMD_H__ +#define __NM_DEFAULT_SYSTEMD_H__ + +/*****************************************************************************/ + +#include "libnm-glib-aux/nm-default-glib.h" + +#undef NETWORKMANAGER_COMPILATION +#define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_SYSTEMD + +/*****************************************************************************/ + +#endif /* __NM_DEFAULT_SYSTEMD_H__ */ diff --git a/src/libnm-systemd-core/nm-sd-utils-core.c b/src/libnm-systemd-core/nm-sd-utils-core.c new file mode 100644 index 00000000..21e8a304 --- /dev/null +++ b/src/libnm-systemd-core/nm-sd-utils-core.c @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "libnm-systemd-core/nm-default-systemd-core.h" + +#include "nm-sd-utils-core.h" + +#include "libnm-glib-aux/nm-uuid.h" + +#include "nm-sd-adapt-core.h" + +#include "sd-id128.h" + +/*****************************************************************************/ + +NMUuid * +nm_sd_utils_id128_get_machine(NMUuid *out_uuid) +{ + g_assert(out_uuid); + + G_STATIC_ASSERT_EXPR(sizeof(*out_uuid) == sizeof(sd_id128_t)); + if (sd_id128_get_machine((sd_id128_t *) out_uuid) < 0) + return NULL; + return out_uuid; +} diff --git a/src/libnm-systemd-core/nm-sd-utils-core.h b/src/libnm-systemd-core/nm-sd-utils-core.h new file mode 100644 index 00000000..ccad0029 --- /dev/null +++ b/src/libnm-systemd-core/nm-sd-utils-core.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_SD_UTILS_CORE_H__ +#define __NM_SD_UTILS_CORE_H__ + +/*****************************************************************************/ + +struct _NMUuid; + +struct _NMUuid *nm_sd_utils_id128_get_machine(struct _NMUuid *out_uuid); + +/*****************************************************************************/ + +#endif /* __NM_SD_UTILS_CORE_H__ */ diff --git a/src/libnm-systemd-core/nm-sd-utils-dhcp.c b/src/libnm-systemd-core/nm-sd-utils-dhcp.c new file mode 100644 index 00000000..7fe889fd --- /dev/null +++ b/src/libnm-systemd-core/nm-sd-utils-dhcp.c @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#include "libnm-systemd-core/nm-default-systemd-core.h" + +#include "nm-sd-utils-dhcp.h" + +#include "sd-adapt-core/nm-sd-adapt-core.h" +#include "src/libsystemd-network/dhcp-lease-internal.h" + +int +nm_sd_dhcp_lease_get_private_options(sd_dhcp_lease *lease, nm_sd_dhcp_option **out_options) +{ + struct sd_dhcp_raw_option *raw_option; + int cnt = 0; + + g_return_val_if_fail(lease, -EINVAL); + g_return_val_if_fail(out_options, -EINVAL); + g_return_val_if_fail(*out_options == NULL, -EINVAL); + + if (lease->private_options == NULL) + return -ENODATA; + + LIST_FOREACH(options, raw_option, lease->private_options) + cnt++; + + *out_options = g_new(nm_sd_dhcp_option, cnt); + cnt = 0; + + LIST_FOREACH(options, raw_option, lease->private_options) + { + (*out_options)[cnt].code = raw_option->tag; + (*out_options)[cnt].data = raw_option->data; + (*out_options)[cnt].data_len = raw_option->length; + cnt++; + } + + return cnt; +} diff --git a/src/libnm-systemd-core/nm-sd-utils-dhcp.h b/src/libnm-systemd-core/nm-sd-utils-dhcp.h new file mode 100644 index 00000000..5d126d5b --- /dev/null +++ b/src/libnm-systemd-core/nm-sd-utils-dhcp.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DHCP_SYSTEMD_UTILS_H__ +#define __NETWORKMANAGER_DHCP_SYSTEMD_UTILS_H__ + +#include "nm-sd.h" + +typedef struct { + uint8_t code; + uint8_t data_len; + void * data; +} nm_sd_dhcp_option; + +int nm_sd_dhcp_lease_get_private_options(sd_dhcp_lease *lease, nm_sd_dhcp_option **out_options); + +#endif /* __NETWORKMANAGER_DHCP_SYSTEMD_UTILS_H__ */ diff --git a/src/libnm-systemd-core/nm-sd.c b/src/libnm-systemd-core/nm-sd.c new file mode 100644 index 00000000..9978a159 --- /dev/null +++ b/src/libnm-systemd-core/nm-sd.c @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 - 2016 Red Hat, Inc. + */ + +#include "libnm-systemd-core/nm-default-systemd-core.h" + +#include "nm-sd.h" + +#include "sd-event.h" + +/***************************************************************************** + * Integrating sd_event into glib. Taken and adjusted from + * https://www.freedesktop.org/software/systemd/man/sd_event_get_fd.html + *****************************************************************************/ + +typedef struct SDEventSource { + GSource source; + GPollFD pollfd; + sd_event *event; +} SDEventSource; + +static gboolean +event_prepare(GSource *source, int *timeout_) +{ + return sd_event_prepare(((SDEventSource *) source)->event) > 0; +} + +static gboolean +event_check(GSource *source) +{ + return sd_event_wait(((SDEventSource *) source)->event, 0) > 0; +} + +static gboolean +event_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) +{ + return sd_event_dispatch(((SDEventSource *) source)->event) > 0; +} + +static void +event_finalize(GSource *source) +{ + SDEventSource *s = (SDEventSource *) source; + + sd_event_unref(s->event); +} + +static SDEventSource * +event_create_source(sd_event *event) +{ + static const GSourceFuncs event_funcs = { + .prepare = event_prepare, + .check = event_check, + .dispatch = event_dispatch, + .finalize = event_finalize, + }; + SDEventSource *source; + gboolean is_default_event = FALSE; + int r; + + if (!event) { + is_default_event = TRUE; + r = sd_event_default(&event); + if (r < 0) + g_return_val_if_reached(NULL); + } + + source = (SDEventSource *) g_source_new((GSourceFuncs *) &event_funcs, sizeof(SDEventSource)); + + source->event = is_default_event ? g_steal_pointer(&event) : sd_event_ref(event); + + source->pollfd = (GPollFD){ + .fd = sd_event_get_fd(source->event), + .events = G_IO_IN | G_IO_HUP | G_IO_ERR, + }; + + g_source_add_poll(&source->source, &source->pollfd); + + return source; +} + +static guint +event_attach(sd_event *event, GMainContext *context) +{ + SDEventSource *source; + guint id; + + source = event_create_source(event); + + g_return_val_if_fail(source, 0); + + id = g_source_attach((GSource *) source, context); + g_source_unref((GSource *) source); + + nm_assert(id != 0); + return id; +} + +guint +nm_sd_event_attach_default(void) +{ + return event_attach(NULL, NULL); +} + +/*****************************************************************************/ + +/* ensure that defines in nm-sd.h correspond to the internal defines. */ + +#include "nm-sd-adapt-core.h" +#include "dhcp-lease-internal.h" + +/*****************************************************************************/ diff --git a/src/libnm-systemd-core/nm-sd.h b/src/libnm-systemd-core/nm-sd.h new file mode 100644 index 00000000..f3461974 --- /dev/null +++ b/src/libnm-systemd-core/nm-sd.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 - 2016 Red Hat, Inc. + */ + +#ifndef __NM_SD_H__ +#define __NM_SD_H__ + +#include "src/systemd/sd-dhcp-client.h" +#include "src/systemd/sd-dhcp6-client.h" +#include "src/systemd/sd-lldp.h" +#include "src/systemd/sd-ipv4ll.h" + +/*****************************************************************************/ + +guint nm_sd_event_attach_default(void); + +/***************************************************************************** + * expose internal systemd API + * + * FIXME: don't use any internal systemd API. + *****************************************************************************/ + +struct sd_dhcp_lease; + +int dhcp_lease_save(struct sd_dhcp_lease *lease, const char *lease_file); +int dhcp_lease_load(struct sd_dhcp_lease **ret, const char *lease_file); + +#endif /* __NM_SD_H__ */ diff --git a/src/libnm-systemd-core/sd-adapt-core/condition.h b/src/libnm-systemd-core/sd-adapt-core/condition.h new file mode 100644 index 00000000..d3a6812a --- /dev/null +++ b/src/libnm-systemd-core/sd-adapt-core/condition.h @@ -0,0 +1,5 @@ +#pragma once + +/* dummy header */ + +typedef struct _sd_adapt_Condition Condition; diff --git a/src/libnm-systemd-core/sd-adapt-core/conf-parser.h b/src/libnm-systemd-core/sd-adapt-core/conf-parser.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-core/sd-adapt-core/conf-parser.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/libnm-systemd-core/sd-adapt-core/device-util.h b/src/libnm-systemd-core/sd-adapt-core/device-util.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-core/sd-adapt-core/device-util.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/libnm-systemd-core/sd-adapt-core/khash.h b/src/libnm-systemd-core/sd-adapt-core/khash.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-core/sd-adapt-core/khash.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/libnm-systemd-core/sd-adapt-core/network-util.h b/src/libnm-systemd-core/sd-adapt-core/network-util.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-core/sd-adapt-core/network-util.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.c b/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.c new file mode 100644 index 00000000..e4d2d2e1 --- /dev/null +++ b/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.c @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 - 2016 Red Hat, Inc. + */ + +#include "libnm-systemd-core/nm-default-systemd-core.h" + +#include "nm-sd-adapt-core.h" + +#include "fd-util.h" + +/*****************************************************************************/ + +int +asynchronous_close(int fd) +{ + safe_close(fd); + return -1; +} + +/*****************************************************************************/ diff --git a/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h b/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h new file mode 100644 index 00000000..4aae34b8 --- /dev/null +++ b/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2014 - 2018 Red Hat, Inc. + */ + +#ifndef __NM_SD_ADAPT_CORE_H__ +#define __NM_SD_ADAPT_CORE_H__ + +#include "libnm-systemd-core/nm-default-systemd-core.h" + +#include <stdbool.h> +#include <sys/resource.h> +#include <time.h> + +#undef NETWORKMANAGER_COMPILATION +#include "libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h" +#undef NETWORKMANAGER_COMPILATION +#define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_SYSTEMD + +#ifndef HAVE_SYS_AUXV_H + #define HAVE_SYS_AUXV_H 0 +#endif + +/***************************************************************************** + * The remainder of the header is only enabled when building the systemd code + * itself. + *****************************************************************************/ + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD + + #include <netinet/in.h> + #include <string.h> + #include <stdio.h> + #include <errno.h> + #include <elf.h> + #ifdef HAVE_SYS_AUXV_H + #include <sys/auxv.h> + #endif + #include <unistd.h> + #include <sys/syscall.h> + #include <sys/ioctl.h> + + /* Missing in Linux 3.2.0, in Ubuntu 12.04 */ + #ifndef BPF_XOR + #define BPF_XOR 0xa0 + #endif + + #ifndef ETHERTYPE_LLDP + #define ETHERTYPE_LLDP 0x88cc + #endif + + #ifndef HAVE_SECURE_GETENV + #ifdef HAVE___SECURE_GETENV + #define secure_getenv __secure_getenv + #else + #error neither secure_getenv nor __secure_getenv is available + #endif + #endif + + #include "libnm-base/nm-base.h" + +/*****************************************************************************/ + +static inline int +sd_notify(int unset_environment, const char *state) +{ + return 0; +} + + /* Can't include both net/if.h and linux/if.h; so have to define this here */ + #ifndef IF_NAMESIZE + #define IF_NAMESIZE 16 + #endif + + #ifndef IFNAMSIZ + #define IFNAMSIZ IF_NAMESIZE + #endif + + #ifndef MAX_HANDLE_SZ + #define MAX_HANDLE_SZ 128 + #endif + + #include "sd-id128.h" + #include "sparse-endian.h" + #include "async.h" + #include "util.h" + +#endif /* (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD */ + +#endif /* __NM_SD_ADAPT_CORE_H__ */ diff --git a/src/libnm-systemd-core/sd-adapt-core/sd-daemon.h b/src/libnm-systemd-core/sd-adapt-core/sd-daemon.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-core/sd-adapt-core/sd-daemon.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/libnm-systemd-core/sd-adapt-core/sd-device.h b/src/libnm-systemd-core/sd-adapt-core/sd-device.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-core/sd-adapt-core/sd-device.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/libnm-systemd-core/sd-adapt-core/udev-util.h b/src/libnm-systemd-core/sd-adapt-core/udev-util.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-core/sd-adapt-core/udev-util.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/libnm-systemd-core/src/libsystemd-network/arp-util.c b/src/libnm-systemd-core/src/libsystemd-network/arp-util.c new file mode 100644 index 00000000..1c777d8a --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/arp-util.c @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014 Axis Communications AB. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include <arpa/inet.h> +#include <linux/filter.h> +#include <netinet/if_ether.h> + +#include "arp-util.h" +#include "fd-util.h" +#include "unaligned.h" +#include "util.h" + +int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_addr *eth_mac) { + struct sock_filter filter[] = { + BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), /* packet >= arp packet ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)), /* A <- header */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), /* header == ethernet ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)), /* A <- protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0), /* protocol == IP ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hln)), /* A <- hardware address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct ether_addr), 1, 0), /* length == sizeof(ether_addr)? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pln)), /* A <- protocol address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct in_addr), 1, 0), /* length == sizeof(in_addr) ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)), /* A <- operation */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), /* protocol == request ? */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0), /* protocol == reply ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + /* Sender Hardware Address must be different from our own */ + BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be32(ð_mac->ether_addr_octet[0])),/* A <- 4 bytes of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_sha)), /* A <- 4 bytes of SHA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 6), /* A == 0 ? */ + BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be16(ð_mac->ether_addr_octet[4])),/* A <- remainder of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, arp_sha) + 4), /* A <- remainder of SHA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + /* Sender Protocol Address or Target Protocol Address must be equal to the one we care about */ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)), /* A <- SPA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)), /* A <- TPA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + }; + struct sock_fprog fprog = { + .len = ELEMENTSOF(filter), + .filter = (struct sock_filter*) filter + }; + union sockaddr_union link = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_ARP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = ETH_ALEN, + .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + }; + _cleanup_close_ int s = -1; + int r; + + assert(ifindex > 0); + + s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + r = bind(s, &link.sa, sizeof(link.ll)); + if (r < 0) + return -errno; + + return TAKE_FD(s); +} + +static int arp_send_packet(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha, + bool announce) { + union sockaddr_union link = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_ARP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = ETH_ALEN, + .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + }; + struct ether_arp arp = { + .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), /* HTYPE */ + .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), /* PTYPE */ + .ea_hdr.ar_hln = ETH_ALEN, /* HLEN */ + .ea_hdr.ar_pln = sizeof(be32_t), /* PLEN */ + .ea_hdr.ar_op = htobe16(ARPOP_REQUEST), /* REQUEST */ + }; + int r; + + assert(fd >= 0); + assert(pa != 0); + assert(ha); + + memcpy(&arp.arp_sha, ha, ETH_ALEN); + memcpy(&arp.arp_tpa, &pa, sizeof(pa)); + + if (announce) + memcpy(&arp.arp_spa, &pa, sizeof(pa)); + + r = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll)); + if (r < 0) + return -errno; + + return 0; +} + +int arp_send_probe(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + return arp_send_packet(fd, ifindex, pa, ha, false); +} + +int arp_send_announcement(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + return arp_send_packet(fd, ifindex, pa, ha, true); +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/arp-util.h b/src/libnm-systemd-core/src/libsystemd-network/arp-util.h new file mode 100644 index 00000000..2dac8cfb --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/arp-util.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2014 Axis Communications AB. All rights reserved. +***/ + +#include <net/ethernet.h> + +#include "socket-util.h" +#include "sparse-endian.h" + +int arp_network_bind_raw_socket(int index, be32_t address, const struct ether_addr *eth_mac); + +int arp_send_probe(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha); +int arp_send_announcement(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha); diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c new file mode 100644 index 00000000..0720e381 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c @@ -0,0 +1,228 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include <linux/if_infiniband.h> +#include <net/ethernet.h> +#include <net/if_arp.h> + +#include "sd-device.h" +#include "sd-id128.h" + +#include "dhcp-identifier.h" +#include "dhcp6-protocol.h" +#include "network-util.h" +#include "siphash24.h" +#include "sparse-endian.h" +#include "stdio-util.h" +#include "udev-util.h" +#include "virt.h" + +#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09) +#define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03) +#define USEC_2000 ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */ + +int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict) { + struct duid d; + + assert_cc(sizeof(d.raw) >= MAX_DUID_LEN); + if (duid_len > MAX_DUID_LEN) + return -EINVAL; + + if (!strict) + /* Strict validation is not requested. We only ensure that the + * DUID is not too long. */ + return 0; + + switch (duid_type) { + case DUID_TYPE_LLT: + if (duid_len <= sizeof(d.llt)) + return -EINVAL; + break; + case DUID_TYPE_EN: + if (duid_len != sizeof(d.en)) + return -EINVAL; + break; + case DUID_TYPE_LL: + if (duid_len <= sizeof(d.ll)) + return -EINVAL; + break; + case DUID_TYPE_UUID: + if (duid_len != sizeof(d.uuid)) + return -EINVAL; + break; + default: + /* accept unknown type in order to be forward compatible */ + break; + } + return 0; +} + +#if 0 /* NM_IGNORED */ +int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) { + uint16_t time_from_2000y; + + assert(duid); + assert(len); + assert(addr); + + if (arp_type == ARPHRD_ETHER) + assert_return(addr_len == ETH_ALEN, -EINVAL); + else if (arp_type == ARPHRD_INFINIBAND) + assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); + else + return -EINVAL; + + if (t < USEC_2000) + time_from_2000y = 0; + else + time_from_2000y = (uint16_t) (((t - USEC_2000) / USEC_PER_SEC) & 0xffffffff); + + unaligned_write_be16(&duid->type, DUID_TYPE_LLT); + unaligned_write_be16(&duid->llt.htype, arp_type); + unaligned_write_be32(&duid->llt.time, time_from_2000y); + memcpy(duid->llt.haddr, addr, addr_len); + + *len = sizeof(duid->type) + sizeof(duid->llt.htype) + sizeof(duid->llt.time) + addr_len; + + return 0; +} + +int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) { + assert(duid); + assert(len); + assert(addr); + + if (arp_type == ARPHRD_ETHER) + assert_return(addr_len == ETH_ALEN, -EINVAL); + else if (arp_type == ARPHRD_INFINIBAND) + assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); + else + return -EINVAL; + + unaligned_write_be16(&duid->type, DUID_TYPE_LL); + unaligned_write_be16(&duid->ll.htype, arp_type); + memcpy(duid->ll.haddr, addr, addr_len); + + *len = sizeof(duid->type) + sizeof(duid->ll.htype) + addr_len; + + return 0; +} +#endif /* NM_IGNORED */ + +int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) { + sd_id128_t machine_id; + uint64_t hash; + int r; + + assert(duid); + assert(len); + + r = sd_id128_get_machine(&machine_id); + if (r < 0) { +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10); +#else + return r; +#endif + } + + unaligned_write_be16(&duid->type, DUID_TYPE_EN); + unaligned_write_be32(&duid->en.pen, SYSTEMD_PEN); + + *len = sizeof(duid->type) + sizeof(duid->en); + + /* a bit of snake-oil perhaps, but no need to expose the machine-id + * directly; duid->en.id might not be aligned, so we need to copy */ + hash = htole64(siphash24(&machine_id, sizeof(machine_id), HASH_KEY.bytes)); + memcpy(duid->en.id, &hash, sizeof(duid->en.id)); + + return 0; +} + +#if 0 /* NM_IGNORED */ +int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len) { + sd_id128_t machine_id; + int r; + + assert(duid); + assert(len); + + r = sd_id128_get_machine_app_specific(APPLICATION_ID, &machine_id); + if (r < 0) + return r; + + unaligned_write_be16(&duid->type, DUID_TYPE_UUID); + memcpy(&duid->raw.data, &machine_id, sizeof(machine_id)); + + *len = sizeof(duid->type) + sizeof(machine_id); + + return 0; +} +#endif + +int dhcp_identifier_set_iaid( + int ifindex, + const uint8_t *mac, + size_t mac_len, + bool legacy_unstable_byteorder, + void *_id) { +#if 0 /* NM_IGNORED */ + /* name is a pointer to memory in the sd_device struct, so must + * have the same scope */ + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + const char *name = NULL; + uint64_t id; + uint32_t id32; + + if (detect_container() <= 0) { + /* not in a container, udev will be around */ + char ifindex_str[1 + DECIMAL_STR_MAX(int)]; + int r; + + xsprintf(ifindex_str, "n%d", ifindex); + if (sd_device_new_from_device_id(&device, ifindex_str) >= 0) { + r = sd_device_get_is_initialized(device); + if (r < 0) + return r; + if (r == 0) + /* not yet ready */ + return -EBUSY; + + r = device_is_renaming(device); + if (r < 0) + return r; + if (r > 0) + /* device is under renaming */ + return -EBUSY; + + name = net_get_name_persistent(device); + } + } + + if (name) + id = siphash24(name, strlen(name), HASH_KEY.bytes); + else + /* fall back to MAC address if no predictable name available */ + id = siphash24(mac, mac_len, HASH_KEY.bytes); + + id32 = (id & 0xffffffff) ^ (id >> 32); + + if (legacy_unstable_byteorder) + /* for historical reasons (a bug), the bits were swapped and thus + * the result was endianness dependent. Preserve that behavior. */ + id32 = __bswap_32(id32); + else + /* the fixed behavior returns a stable byte order. Since LE is expected + * to be more common, swap the bytes on LE to give the same as legacy + * behavior. */ + id32 = be32toh(id32); + + unaligned_write_ne32(_id, id32); + return 0; +#else /* NM_IGNORED */ + /* for NetworkManager, we don't use this function and we should never call here. + * This got replaced by nm_utils_create_dhcp_iaid(). */ + g_return_val_if_reached (-EINVAL); +#endif /* NM_IGNORED */ +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h new file mode 100644 index 00000000..1d9a9c55 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-id128.h" + +#include "macro.h" +#include "sparse-endian.h" +#include "time-util.h" +#include "unaligned.h" + +#define SYSTEMD_PEN 43793 + +typedef enum DUIDType { + DUID_TYPE_LLT = 1, + DUID_TYPE_EN = 2, + DUID_TYPE_LL = 3, + DUID_TYPE_UUID = 4, + _DUID_TYPE_MAX, + _DUID_TYPE_INVALID = -EINVAL, +} DUIDType; + +/* RFC 3315 section 9.1: + * A DUID can be no more than 128 octets long (not including the type code). + */ +#define MAX_DUID_LEN 128 + +/* https://tools.ietf.org/html/rfc3315#section-9.1 */ +struct duid { + be16_t type; + union { + struct { + /* DUID_TYPE_LLT */ + be16_t htype; + be32_t time; + uint8_t haddr[0]; + } _packed_ llt; + struct { + /* DUID_TYPE_EN */ + be32_t pen; + uint8_t id[8]; + } _packed_ en; + struct { + /* DUID_TYPE_LL */ + be16_t htype; + uint8_t haddr[0]; + } _packed_ ll; + struct { + /* DUID_TYPE_UUID */ + sd_id128_t uuid; + } _packed_ uuid; + struct { + uint8_t data[MAX_DUID_LEN]; + } _packed_ raw; + }; +} _packed_; + +int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict); +int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len); +int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len); +int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len); +int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len); +int dhcp_identifier_set_iaid(int ifindex, const uint8_t *mac, size_t mac_len, bool legacy_unstable_byteorder, void *_id); diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-internal.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp-internal.h new file mode 100644 index 00000000..d8c42757 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-internal.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include <linux/if_packet.h> +#include <net/ethernet.h> +#include <stdint.h> + +#include "sd-dhcp-client.h" + +#include "dhcp-protocol.h" +#include "log-link.h" +#include "socket-util.h" + +typedef struct sd_dhcp_option { + unsigned n_ref; + + uint8_t option; + void *data; + size_t length; +} sd_dhcp_option; + +typedef struct DHCPServerData { + struct in_addr *addr; + size_t size; +} DHCPServerData; + +extern const struct hash_ops dhcp_option_hash_ops; + +int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link, uint32_t xid, + const uint8_t *mac_addr, size_t mac_addr_len, + const uint8_t *bcast_addr, size_t bcast_addr_len, + uint16_t arp_type, uint16_t port); +int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type); +int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, + const void *packet, size_t len); +int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, + const void *packet, size_t len); + +int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, uint8_t overload, + uint8_t code, size_t optlen, const void *optval); +int dhcp_option_find_option(uint8_t *options, size_t length, uint8_t wanted_code, size_t *ret_offset); +int dhcp_option_remove_option(uint8_t *options, size_t buflen, uint8_t option_code); + +typedef int (*dhcp_option_callback_t)(uint8_t code, uint8_t len, + const void *option, void *userdata); + +int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **error_message); + +int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, + uint8_t type, uint16_t arp_type, size_t optlen, + size_t *optoffset); + +uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len); + +void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr, + uint16_t source, be32_t destination_addr, + uint16_t destination, uint16_t len, int ip_service_type); + +int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port); + +/* If we are invoking callbacks of a dhcp-client, ensure unreffing the + * client from the callback doesn't destroy the object we are working + * on */ +#define DHCP_CLIENT_DONT_DESTROY(client) \ + _cleanup_(sd_dhcp_client_unrefp) _unused_ sd_dhcp_client *_dont_destroy_##client = sd_dhcp_client_ref(client) + +#define log_dhcp_client_errno(client, error, fmt, ...) \ + log_interface_prefix_full_errno( \ + "DHCPv4 client: ", \ + sd_dhcp_client_get_ifname(client), \ + error, fmt, ##__VA_ARGS__) +#define log_dhcp_client(client, fmt, ...) \ + log_interface_prefix_full_errno_zerook( \ + "DHCPv4 client: ", \ + sd_dhcp_client_get_ifname(client), \ + 0, fmt, ##__VA_ARGS__) diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-lease-internal.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp-lease-internal.h new file mode 100644 index 00000000..35a74dd7 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-lease-internal.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include "sd-dhcp-client.h" + +#include "dhcp-internal.h" +#include "dhcp-protocol.h" +#include "list.h" +#include "util.h" + +struct sd_dhcp_route { + struct in_addr dst_addr; + struct in_addr gw_addr; + unsigned char dst_prefixlen; + + uint8_t option; +}; + +struct sd_dhcp_raw_option { + LIST_FIELDS(struct sd_dhcp_raw_option, options); + + uint8_t tag; + uint8_t length; + void *data; +}; + +struct sd_dhcp_lease { + unsigned n_ref; + + /* each 0 if unset */ + uint32_t t1; + uint32_t t2; + uint32_t lifetime; + + /* each 0 if unset */ + be32_t address; + be32_t server_address; + be32_t next_server; + + bool have_subnet_mask; + be32_t subnet_mask; + + bool have_broadcast; + be32_t broadcast; + + struct in_addr *router; + size_t router_size; + + DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX]; + + struct sd_dhcp_route *static_route; + size_t static_route_size; + + uint16_t mtu; /* 0 if unset */ + + char *domainname; + char **search_domains; + char *hostname; + char *root_path; + + void *client_id; + size_t client_id_len; + + void *vendor_specific; + size_t vendor_specific_len; + + char *timezone; + + LIST_HEAD(struct sd_dhcp_raw_option, private_options); +}; + +int dhcp_lease_new(sd_dhcp_lease **ret); + +int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata); +int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains); +int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len); + +int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease); + +int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len); diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-network.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp-network.c new file mode 100644 index 00000000..bbfdeb0f --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-network.c @@ -0,0 +1,249 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include <errno.h> +#include <net/ethernet.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <stdio.h> +#include <string.h> +#include <linux/filter.h> +#include <linux/if_infiniband.h> +#include <linux/if_packet.h> + +#include "dhcp-internal.h" +#include "fd-util.h" +#include "socket-util.h" +#include "unaligned.h" + +static int _bind_raw_socket(int ifindex, union sockaddr_union *link, + uint32_t xid, + const uint8_t *bcast_addr, + size_t bcast_addr_len, + const struct ether_addr *eth_mac, + uint16_t arp_type, uint8_t dhcp_hlen, + uint16_t port) { + struct sock_filter filter[] = { + BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(DHCPPacket), 1, 0), /* packet >= DHCPPacket ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.protocol)), /* A <- IP protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), /* IP protocol == UDP ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags */ + BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x20), /* A <- A & 0x20 (More Fragments bit) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags + Fragment offset */ + BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x1fff), /* A <- A & 0x1fff (Fragment offset) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, udp.dest)), /* A <- UDP destination port */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, port, 1, 0), /* UDP destination port == DHCP client port ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.op)), /* A <- DHCP op */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.htype)), /* A <- DHCP header type */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arp_type, 1, 0), /* header type == arp_type ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.xid)), /* A <- client identifier */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, xid, 1, 0), /* client identifier == xid ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.hlen)), /* A <- MAC address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, dhcp_hlen, 1, 0), /* address length == dhcp_hlen ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* We only support MAC address length to be either 0 or 6 (ETH_ALEN). Optionally + * compare chaddr for ETH_ALEN bytes. */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_ALEN, 0, 12), /* A (the MAC address length) == ETH_ALEN ? */ + BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be32(ð_mac->ether_addr_octet[0])), /* A <- 4 bytes of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr)), /* A <- 4 bytes of MAC from dhcp.chaddr */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be16(ð_mac->ether_addr_octet[4])), /* A <- remainder of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr) + 4), /* A <- remainder of MAC from dhcp.chaddr */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.magic)), /* A <- DHCP magic cookie */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_MAGIC_COOKIE, 1, 0), /* cookie == DHCP magic cookie ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + }; + struct sock_fprog fprog = { + .len = ELEMENTSOF(filter), + .filter = filter + }; + _cleanup_close_ int s = -1; + int r; + + assert(ifindex > 0); + assert(link); + + s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -errno; + + r = setsockopt_int(s, SOL_PACKET, PACKET_AUXDATA, true); + if (r < 0) + return r; + + r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + link->ll = (struct sockaddr_ll) { + .sll_family = AF_PACKET, + .sll_protocol = htobe16(ETH_P_IP), + .sll_ifindex = ifindex, + .sll_hatype = htobe16(arp_type), + .sll_halen = bcast_addr_len, + }; + memcpy(link->ll.sll_addr, bcast_addr, bcast_addr_len); /* We may overflow link->ll. link->ll_buffer ensures we have enough space. */ + + r = bind(s, &link->sa, SOCKADDR_LL_LEN(link->ll)); + if (r < 0) + return -errno; + + return TAKE_FD(s); +} + +int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link, uint32_t xid, + const uint8_t *mac_addr, size_t mac_addr_len, + const uint8_t *bcast_addr, size_t bcast_addr_len, + uint16_t arp_type, uint16_t port) { + static const uint8_t eth_bcast[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + /* Default broadcast address for IPoIB */ + static const uint8_t ib_bcast[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, 0x40, 0x1b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff + }; + struct ether_addr eth_mac = { { 0, 0, 0, 0, 0, 0 } }; + const uint8_t *default_bcast_addr; + size_t expected_bcast_addr_len; + uint8_t dhcp_hlen = 0; + + if (arp_type == ARPHRD_ETHER) { + assert_return(mac_addr_len == ETH_ALEN, -EINVAL); + memcpy(ð_mac, mac_addr, ETH_ALEN); + dhcp_hlen = ETH_ALEN; + + default_bcast_addr = eth_bcast; + expected_bcast_addr_len = ETH_ALEN; + } else if (arp_type == ARPHRD_INFINIBAND) { + default_bcast_addr = ib_bcast; + expected_bcast_addr_len = INFINIBAND_ALEN; + } else + return -EINVAL; + + if (bcast_addr && bcast_addr_len > 0) + assert_return(bcast_addr_len == expected_bcast_addr_len, -EINVAL); + else { + bcast_addr = default_bcast_addr; + bcast_addr_len = expected_bcast_addr_len; + } + + return _bind_raw_socket(ifindex, link, xid, bcast_addr, bcast_addr_len, + ð_mac, arp_type, dhcp_hlen, port); +} + +int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) { + union sockaddr_union src = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(port), + .in.sin_addr.s_addr = address, + }; + _cleanup_close_ int s = -1; + int r; + + s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -errno; + + if (ip_service_type >= 0) + r = setsockopt_int(s, IPPROTO_IP, IP_TOS, ip_service_type); + else + r = setsockopt_int(s, IPPROTO_IP, IP_TOS, IPTOS_CLASS_CS6); + + if (r < 0) + return r; + + r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true); + if (r < 0) + return r; + + if (ifindex > 0) { + r = socket_bind_to_ifindex(s, ifindex); + if (r < 0) + return r; + } + + if (port == DHCP_PORT_SERVER) { + r = setsockopt_int(s, SOL_SOCKET, SO_BROADCAST, true); + if (r < 0) + return r; + if (address == INADDR_ANY) { + /* IP_PKTINFO filter should not be applied when packets are + allowed to enter/leave through the interface other than + DHCP server sits on(BindToInterface option). */ + r = setsockopt_int(s, IPPROTO_IP, IP_PKTINFO, true); + if (r < 0) + return r; + } + } else { + r = setsockopt_int(s, IPPROTO_IP, IP_FREEBIND, true); + if (r < 0) + return r; + } + + r = bind(s, &src.sa, sizeof(src.in)); + if (r < 0) + return -errno; + + return TAKE_FD(s); +} + +int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, + const void *packet, size_t len) { + int r; + + assert(link); + assert(packet); + assert(len); + + r = sendto(s, packet, len, 0, &link->sa, SOCKADDR_LL_LEN(link->ll)); + if (r < 0) + return -errno; + + return 0; +} + +int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, + const void *packet, size_t len) { + union sockaddr_union dest = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(port), + .in.sin_addr.s_addr = address, + }; + int r; + + assert(s >= 0); + assert(packet); + assert(len); + + r = sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in)); + if (r < 0) + return -errno; + + return 0; +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-option.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp-option.c new file mode 100644 index 00000000..1710f696 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-option.c @@ -0,0 +1,444 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> + +#include "alloc-util.h" +#include "dhcp-internal.h" +#include "dhcp-server-internal.h" +#include "memory-util.h" +#include "strv.h" +#include "utf8.h" + +/* Append type-length value structure to the options buffer */ +static int dhcp_option_append_tlv(uint8_t options[], size_t size, size_t *offset, uint8_t code, size_t optlen, const void *optval) { + assert(options); + assert(size > 0); + assert(offset); + assert(optlen <= UINT8_MAX); + assert(*offset < size); + + if (*offset + 2 + optlen > size) + return -ENOBUFS; + + options[*offset] = code; + options[*offset + 1] = optlen; + + memcpy_safe(&options[*offset + 2], optval, optlen); + *offset += 2 + optlen; + return 0; +} + +static int option_append(uint8_t options[], size_t size, size_t *offset, + uint8_t code, size_t optlen, const void *optval) { + assert(options); + assert(size > 0); + assert(offset); + + int r; + + if (code != SD_DHCP_OPTION_END) + /* always make sure there is space for an END option */ + size--; + + switch (code) { + + case SD_DHCP_OPTION_PAD: + case SD_DHCP_OPTION_END: + if (*offset + 1 > size) + return -ENOBUFS; + + options[*offset] = code; + *offset += 1; + break; + + case SD_DHCP_OPTION_USER_CLASS: { + size_t total = 0; + char **s; + + if (strv_isempty((char **) optval)) + return -EINVAL; + + STRV_FOREACH(s, (char **) optval) { + size_t len = strlen(*s); + + if (len > 255 || len == 0) + return -EINVAL; + + total += 1 + len; + } + + if (*offset + 2 + total > size) + return -ENOBUFS; + + options[*offset] = code; + options[*offset + 1] = total; + *offset += 2; + + STRV_FOREACH(s, (char **) optval) { + size_t len = strlen(*s); + + options[*offset] = len; + memcpy(&options[*offset + 1], *s, len); + *offset += 1 + len; + } + + break; + } + case SD_DHCP_OPTION_SIP_SERVER: + if (*offset + 3 + optlen > size) + return -ENOBUFS; + + options[*offset] = code; + options[*offset + 1] = optlen + 1; + options[*offset + 2] = 1; + + memcpy_safe(&options[*offset + 3], optval, optlen); + *offset += 3 + optlen; + + break; + case SD_DHCP_OPTION_VENDOR_SPECIFIC: { + OrderedSet *s = (OrderedSet *) optval; + struct sd_dhcp_option *p; + size_t l = 0; + + ORDERED_SET_FOREACH(p, s) + l += p->length + 2; + + if (*offset + l + 2 > size) + return -ENOBUFS; + + options[*offset] = code; + options[*offset + 1] = l; + *offset += 2; + + ORDERED_SET_FOREACH(p, s) { + r = dhcp_option_append_tlv(options, size, offset, p->option, p->length, p->data); + if (r < 0) + return r; + } + break; + } + case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: { +#if 0 /* NM_IGNORED */ + sd_dhcp_server *server = (sd_dhcp_server *) optval; + size_t current_offset = *offset + 2; + + if (server->agent_circuit_id) { + r = dhcp_option_append_tlv(options, size, ¤t_offset, SD_DHCP_RELAY_AGENT_CIRCUIT_ID, + strlen(server->agent_circuit_id), server->agent_circuit_id); + if (r < 0) + return r; + } + if (server->agent_remote_id) { + r = dhcp_option_append_tlv(options, size, ¤t_offset, SD_DHCP_RELAY_AGENT_REMOTE_ID, + strlen(server->agent_remote_id), server->agent_remote_id); + if (r < 0) + return r; + } + + options[*offset] = code; + options[*offset + 1] = current_offset - *offset - 2; + assert(current_offset - *offset - 2 <= UINT8_MAX); + *offset = current_offset; + break; +#endif /* NM_IGNORED */ + g_return_val_if_reached(-EINVAL); + } + default: + return dhcp_option_append_tlv(options, size, offset, code, optlen, optval); + } + return 0; +} + +static int option_length(uint8_t *options, size_t length, size_t offset) { + assert(options); + assert(offset < length); + + if (IN_SET(options[offset], SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END)) + return 1; + if (length < offset + 2) + return -ENOBUFS; + + /* validating that buffer is long enough */ + if (length < offset + 2 + options[offset + 1]) + return -ENOBUFS; + + return options[offset + 1] + 2; +} + +int dhcp_option_find_option(uint8_t *options, size_t length, uint8_t code, size_t *ret_offset) { + int r; + + assert(options); + assert(ret_offset); + + for (size_t offset = 0; offset < length; offset += r) { + r = option_length(options, length, offset); + if (r < 0) + return r; + + if (code == options[offset]) { + *ret_offset = offset; + return r; + } + } + return -ENOENT; +} + +int dhcp_option_remove_option(uint8_t *options, size_t length, uint8_t option_code) { + int r; + size_t offset; + + assert(options); + + r = dhcp_option_find_option(options, length, option_code, &offset); + if (r < 0) + return r; + + memmove(options + offset, options + offset + r, length - offset - r); + return length - r; +} + +int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, + uint8_t overload, + uint8_t code, size_t optlen, const void *optval) { + const bool use_file = overload & DHCP_OVERLOAD_FILE; + const bool use_sname = overload & DHCP_OVERLOAD_SNAME; + int r; + + assert(message); + assert(offset); + + /* If *offset is in range [0, size), we are writing to ->options, + * if *offset is in range [size, size + sizeof(message->file)) and use_file, we are writing to ->file, + * if *offset is in range [size + use_file*sizeof(message->file), size + use_file*sizeof(message->file) + sizeof(message->sname)) + * and use_sname, we are writing to ->sname. + */ + + if (*offset < size) { + /* still space in the options array */ + r = option_append(message->options, size, offset, code, optlen, optval); + if (r >= 0) + return 0; + else if (r == -ENOBUFS && (use_file || use_sname)) { + /* did not fit, but we have more buffers to try + close the options array and move the offset to its end */ + r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + *offset = size; + } else + return r; + } + + if (use_file) { + size_t file_offset = *offset - size; + + if (file_offset < sizeof(message->file)) { + /* still space in the 'file' array */ + r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval); + if (r >= 0) { + *offset = size + file_offset; + return 0; + } else if (r == -ENOBUFS && use_sname) { + /* did not fit, but we have more buffers to try + close the file array and move the offset to its end */ + r = option_append(message->file, sizeof(message->file), &file_offset, SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + *offset = size + sizeof(message->file); + } else + return r; + } + } + + if (use_sname) { + size_t sname_offset = *offset - size - use_file*sizeof(message->file); + + if (sname_offset < sizeof(message->sname)) { + /* still space in the 'sname' array */ + r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval); + if (r >= 0) { + *offset = size + use_file*sizeof(message->file) + sname_offset; + return 0; + } else + /* no space, or other error, give up */ + return r; + } + } + + return -ENOBUFS; +} + +static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload, + uint8_t *message_type, char **error_message, dhcp_option_callback_t cb, + void *userdata) { + uint8_t code, len; + const uint8_t *option; + size_t offset = 0; + + while (offset < buflen) { + code = options[offset ++]; + + switch (code) { + case SD_DHCP_OPTION_PAD: + continue; + + case SD_DHCP_OPTION_END: + return 0; + } + + if (buflen < offset + 1) + return -ENOBUFS; + + len = options[offset ++]; + + if (buflen < offset + len) + return -EINVAL; + + option = &options[offset]; + + switch (code) { + case SD_DHCP_OPTION_MESSAGE_TYPE: + if (len != 1) + return -EINVAL; + + if (message_type) + *message_type = *option; + + break; + + case SD_DHCP_OPTION_ERROR_MESSAGE: + if (len == 0) + return -EINVAL; + + if (error_message) { + _cleanup_free_ char *string = NULL; + + /* Accept a trailing NUL byte */ + if (memchr(option, 0, len - 1)) + return -EINVAL; + + string = memdup_suffix0((const char *) option, len); + if (!string) + return -ENOMEM; + + if (!ascii_is_valid(string)) + return -EINVAL; + + free_and_replace(*error_message, string); + } + + break; + case SD_DHCP_OPTION_OVERLOAD: + if (len != 1) + return -EINVAL; + + if (overload) + *overload = *option; + + break; + + default: + if (cb) + cb(code, len, option, userdata); + + break; + } + + offset += len; + } + + if (offset < buflen) + return -EINVAL; + + return 0; +} + +int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) { + _cleanup_free_ char *error_message = NULL; + uint8_t overload = 0; + uint8_t message_type = 0; + int r; + + if (!message) + return -EINVAL; + + if (len < sizeof(DHCPMessage)) + return -EINVAL; + + len -= sizeof(DHCPMessage); + + r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata); + if (r < 0) + return r; + + if (overload & DHCP_OVERLOAD_FILE) { + r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata); + if (r < 0) + return r; + } + + if (overload & DHCP_OVERLOAD_SNAME) { + r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata); + if (r < 0) + return r; + } + + if (message_type == 0) + return -ENOMSG; + + if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE)) + *_error_message = TAKE_PTR(error_message); + + return message_type; +} + +static sd_dhcp_option* dhcp_option_free(sd_dhcp_option *i) { + if (!i) + return NULL; + + free(i->data); + return mfree(i); +} + +int sd_dhcp_option_new(uint8_t option, const void *data, size_t length, sd_dhcp_option **ret) { + assert_return(ret, -EINVAL); + assert_return(length == 0 || data, -EINVAL); + + _cleanup_free_ void *q = memdup(data, length); + if (!q) + return -ENOMEM; + + sd_dhcp_option *p = new(sd_dhcp_option, 1); + if (!p) + return -ENOMEM; + + *p = (sd_dhcp_option) { + .n_ref = 1, + .option = option, + .length = length, + .data = TAKE_PTR(q), + }; + + *ret = TAKE_PTR(p); + return 0; +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_option, sd_dhcp_option, dhcp_option_free); +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + dhcp_option_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + sd_dhcp_option, + sd_dhcp_option_unref); diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-packet.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp-packet.c new file mode 100644 index 00000000..eeadd562 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-packet.c @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include <errno.h> +#include <net/ethernet.h> +#include <net/if_arp.h> +#include <string.h> + +#include "dhcp-internal.h" +#include "dhcp-protocol.h" + +#define DHCP_CLIENT_MIN_OPTIONS_SIZE 312 + +int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, + uint8_t type, uint16_t arp_type, size_t optlen, + size_t *optoffset) { + size_t offset = 0; + int r; + + assert(IN_SET(op, BOOTREQUEST, BOOTREPLY)); + assert(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND)); + + message->op = op; + message->htype = arp_type; + message->hlen = (arp_type == ARPHRD_ETHER) ? ETHER_ADDR_LEN : 0; + message->xid = htobe32(xid); + message->magic = htobe32(DHCP_MAGIC_COOKIE); + + r = dhcp_option_append(message, optlen, &offset, 0, + SD_DHCP_OPTION_MESSAGE_TYPE, 1, &type); + if (r < 0) + return r; + + *optoffset = offset; + + return 0; +} + +uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len) { + uint64_t *buf_64 = (uint64_t*)buf; + uint64_t *end_64 = buf_64 + (len / sizeof(uint64_t)); + uint64_t sum = 0; + + /* See RFC1071 */ + + while (buf_64 < end_64) { + sum += *buf_64; + if (sum < *buf_64) + /* wrap around in one's complement */ + sum++; + + buf_64++; + } + + if (len % sizeof(uint64_t)) { + /* If the buffer is not aligned to 64-bit, we need + to zero-pad the last few bytes and add them in */ + uint64_t buf_tail = 0; + + memcpy(&buf_tail, buf_64, len % sizeof(uint64_t)); + + sum += buf_tail; + if (sum < buf_tail) + /* wrap around */ + sum++; + } + + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return ~sum; +} + +void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr, + uint16_t source_port, be32_t destination_addr, + uint16_t destination_port, uint16_t len, int ip_service_type) { + packet->ip.version = IPVERSION; + packet->ip.ihl = DHCP_IP_SIZE / 4; + packet->ip.tot_len = htobe16(len); + + if (ip_service_type >= 0) + packet->ip.tos = ip_service_type; + else + packet->ip.tos = IPTOS_CLASS_CS6; + + packet->ip.protocol = IPPROTO_UDP; + packet->ip.saddr = source_addr; + packet->ip.daddr = destination_addr; + + packet->udp.source = htobe16(source_port); + packet->udp.dest = htobe16(destination_port); + + packet->udp.len = htobe16(len - DHCP_IP_SIZE); + + packet->ip.check = packet->udp.len; + packet->udp.check = dhcp_packet_checksum((uint8_t*)&packet->ip.ttl, len - 8); + + packet->ip.ttl = IPDEFTTL; + packet->ip.check = 0; + packet->ip.check = dhcp_packet_checksum((uint8_t*)&packet->ip, DHCP_IP_SIZE); +} + +int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) { + size_t hdrlen; + + assert(packet); + + /* IP */ + + if (packet->ip.version != IPVERSION) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: not IPv4"); + + if (packet->ip.ihl < 5) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: IPv4 IHL (%u words) invalid", + packet->ip.ihl); + + hdrlen = packet->ip.ihl * 4; + if (hdrlen < 20) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: IPv4 IHL (%zu bytes) " + "smaller than minimum (20 bytes)", + hdrlen); + + if (len < hdrlen) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: packet (%zu bytes) " + "smaller than expected (%zu) by IP header", + len, hdrlen); + + /* UDP */ + + if (packet->ip.protocol != IPPROTO_UDP) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: not UDP"); + + if (len < hdrlen + be16toh(packet->udp.len)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: packet (%zu bytes) " + "smaller than expected (%zu) by UDP header", + len, hdrlen + be16toh(packet->udp.len)); + + if (be16toh(packet->udp.dest) != port) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: to port %u, which " + "is not the DHCP client port (%u)", + be16toh(packet->udp.dest), port); + + /* checksums - computing these is relatively expensive, so only do it + if all the other checks have passed + */ + + if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: invalid IP checksum"); + + if (checksum && packet->udp.check) { + packet->ip.check = packet->udp.len; + packet->ip.ttl = 0; + + if (dhcp_packet_checksum((uint8_t*)&packet->ip.ttl, + be16toh(packet->udp.len) + 12)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ignoring packet: invalid UDP checksum"); + } + + return 0; +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-protocol.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp-protocol.h new file mode 100644 index 00000000..11f4201a --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-protocol.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include <netinet/ip.h> +#include <netinet/udp.h> +#include <stdint.h> + +#include "macro.h" +#include "sparse-endian.h" + +struct DHCPMessage { + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + be32_t xid; + be16_t secs; + be16_t flags; + be32_t ciaddr; + be32_t yiaddr; + be32_t siaddr; + be32_t giaddr; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; + be32_t magic; + uint8_t options[0]; +} _packed_; + +typedef struct DHCPMessage DHCPMessage; + +struct DHCPPacket { + struct iphdr ip; + struct udphdr udp; + DHCPMessage dhcp; +} _packed_; + +typedef struct DHCPPacket DHCPPacket; + +#define DHCP_IP_SIZE (int32_t)(sizeof(struct iphdr)) +#define DHCP_IP_UDP_SIZE (int32_t)(sizeof(struct udphdr) + DHCP_IP_SIZE) +#define DHCP_MESSAGE_SIZE (int32_t)(sizeof(DHCPMessage)) +#define DHCP_DEFAULT_MIN_SIZE 576 /* the minimum internet hosts must be able to receive */ +#define DHCP_MIN_OPTIONS_SIZE (DHCP_DEFAULT_MIN_SIZE - DHCP_IP_UDP_SIZE - DHCP_MESSAGE_SIZE) +#define DHCP_MAGIC_COOKIE (uint32_t)(0x63825363) + +enum { + DHCP_PORT_SERVER = 67, + DHCP_PORT_CLIENT = 68, +}; + +enum DHCPState { + DHCP_STATE_INIT = 0, + DHCP_STATE_SELECTING = 1, + DHCP_STATE_INIT_REBOOT = 2, + DHCP_STATE_REBOOTING = 3, + DHCP_STATE_REQUESTING = 4, + DHCP_STATE_BOUND = 5, + DHCP_STATE_RENEWING = 6, + DHCP_STATE_REBINDING = 7, + DHCP_STATE_STOPPED = 8, +}; + +typedef enum DHCPState DHCPState; + +enum { + BOOTREQUEST = 1, + BOOTREPLY = 2, +}; + +enum { + DHCP_DISCOVER = 1, + DHCP_OFFER = 2, + DHCP_REQUEST = 3, + DHCP_DECLINE = 4, + DHCP_ACK = 5, + DHCP_NAK = 6, + DHCP_RELEASE = 7, + DHCP_INFORM = 8, + DHCP_FORCERENEW = 9, +}; + +enum { + DHCP_OVERLOAD_FILE = 1, + DHCP_OVERLOAD_SNAME = 2, +}; + +#define DHCP_MAX_FQDN_LENGTH 255 + +enum { + DHCP_FQDN_FLAG_S = (1 << 0), + DHCP_FQDN_FLAG_O = (1 << 1), + DHCP_FQDN_FLAG_E = (1 << 2), + DHCP_FQDN_FLAG_N = (1 << 3), +}; diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h new file mode 100644 index 00000000..34e1f61c --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2014-2015 Intel Corporation. All rights reserved. +***/ + +#include <net/ethernet.h> +#include <netinet/in.h> + +#include "sd-event.h" +#include "sd-dhcp6-client.h" + +#include "hashmap.h" +#include "list.h" +#include "log-link.h" +#include "macro.h" +#include "sparse-endian.h" + +typedef struct sd_dhcp6_option { + unsigned n_ref; + + uint32_t enterprise_identifier; + uint16_t option; + void *data; + size_t length; +} sd_dhcp6_option; + +extern const struct hash_ops dhcp6_option_hash_ops; + +/* Common option header */ +typedef struct DHCP6Option { + be16_t code; + be16_t len; + uint8_t data[]; +} _packed_ DHCP6Option; + +/* Address option */ +struct iaaddr { + struct in6_addr address; + be32_t lifetime_preferred; + be32_t lifetime_valid; +} _packed_; + +/* Prefix Delegation Prefix option */ +struct iapdprefix { + be32_t lifetime_preferred; + be32_t lifetime_valid; + uint8_t prefixlen; + struct in6_addr address; +} _packed_; + +typedef struct DHCP6Address DHCP6Address; + +struct DHCP6Address { + LIST_FIELDS(DHCP6Address, addresses); + + union { + struct iaaddr iaaddr; + struct iapdprefix iapdprefix; + }; +}; + +/* Non-temporary Address option */ +struct ia_na { + be32_t id; + be32_t lifetime_t1; + be32_t lifetime_t2; +} _packed_; + +/* Prefix Delegation option */ +struct ia_pd { + be32_t id; + be32_t lifetime_t1; + be32_t lifetime_t2; +} _packed_; + +/* Temporary Address option */ +struct ia_ta { + be32_t id; +} _packed_; + +typedef struct DHCP6IA { + uint16_t type; + union { + struct ia_na ia_na; + struct ia_pd ia_pd; + struct ia_ta ia_ta; + }; + + LIST_HEAD(DHCP6Address, addresses); +} DHCP6IA; + +int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, + size_t optlen, const void *optval); +int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia); +int dhcp6_option_append_pd(uint8_t **buf, size_t *buflen, const DHCP6IA *pd, const DHCP6Address *hint_pd_prefix); +int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn); +int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class); +int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const *user_class); +int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options); +int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, + size_t *optlen, uint8_t **optvalue); +int dhcp6_option_parse_status(DHCP6Option *option, size_t len); +int dhcp6_option_parse_ia(sd_dhcp6_client *client, DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_status_code); +int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen, + struct in6_addr **addrs, size_t count); +int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen, + char ***str_arr); +int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str); + +int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *address); +int dhcp6_network_send_udp_socket(int s, struct in6_addr *address, + const void *packet, size_t len); + +const char *dhcp6_message_type_to_string(int s) _const_; +int dhcp6_message_type_from_string(const char *s) _pure_; +const char *dhcp6_message_status_to_string(int s) _const_; +int dhcp6_message_status_from_string(const char *s) _pure_; + +#define log_dhcp6_client_errno(client, error, fmt, ...) \ + log_interface_prefix_full_errno( \ + "DHCPv6 client: ", \ + sd_dhcp6_client_get_ifname(client), \ + error, fmt, ##__VA_ARGS__) +#define log_dhcp6_client(client, fmt, ...) \ + log_interface_prefix_full_errno_zerook( \ + "DHCPv6 client: ", \ + sd_dhcp6_client_get_ifname(client), \ + 0, fmt, ##__VA_ARGS__) diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-lease-internal.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-lease-internal.h new file mode 100644 index 00000000..391b4f1f --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-lease-internal.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2014-2015 Intel Corporation. All rights reserved. +***/ + +#include <stdint.h> + +#include "sd-dhcp6-lease.h" + +#include "dhcp6-internal.h" + +struct sd_dhcp6_lease { + unsigned n_ref; + + uint8_t *serverid; + size_t serverid_len; + uint8_t preference; + bool rapid_commit; + + DHCP6IA ia; + DHCP6IA pd; + + DHCP6Address *addr_iter; + DHCP6Address *prefix_iter; + + struct in6_addr *dns; + size_t dns_count; + char **domains; + size_t domains_count; + struct in6_addr *ntp; + size_t ntp_count; + char **ntp_fqdn; + size_t ntp_fqdn_count; + char *fqdn; +}; + +int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire); +DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia); + +int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, + size_t len); +int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **id, size_t *len); +int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference); +int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference); +int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease); +int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit); + +int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid); +int dhcp6_lease_get_pd_iaid(sd_dhcp6_lease *lease, be32_t *iaid); + +int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen); +int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval, + size_t optlen); +int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen); +int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval, + size_t optlen) ; +int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen); + +int dhcp6_lease_new(sd_dhcp6_lease **ret); diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c new file mode 100644 index 00000000..b6816de4 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include <errno.h> +#include <netinet/in.h> +#include <netinet/ip6.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <linux/if_packet.h> + +#include "dhcp6-internal.h" +#include "dhcp6-protocol.h" +#include "fd-util.h" +#include "socket-util.h" + +int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) { + union sockaddr_union src = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(DHCP6_PORT_CLIENT), + .in6.sin6_scope_id = ifindex, + }; + _cleanup_close_ int s = -1; + int r; + + assert(ifindex > 0); + assert(local_address); + + src.in6.sin6_addr = *local_address; + + s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_UDP); + if (s < 0) + return -errno; + + r = setsockopt_int(s, IPPROTO_IPV6, IPV6_V6ONLY, true); + if (r < 0) + return r; + + r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, false); + if (r < 0) + return r; + + r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true); + if (r < 0) + return r; + + r = bind(s, &src.sa, sizeof(src.in6)); + if (r < 0) + return -errno; + + return TAKE_FD(s); +} + +int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, + const void *packet, size_t len) { + union sockaddr_union dest = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(DHCP6_PORT_SERVER), + }; + int r; + + assert(server_address); + + memcpy(&dest.in6.sin6_addr, server_address, sizeof(dest.in6.sin6_addr)); + + r = sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in6)); + if (r < 0) + return -errno; + + return 0; +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c new file mode 100644 index 00000000..d9e24585 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c @@ -0,0 +1,847 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014-2015 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include <errno.h> +#include <netinet/in.h> + +#include "sd-dhcp6-client.h" + +#include "alloc-util.h" +#include "dhcp-identifier.h" +#include "dhcp6-internal.h" +#include "dhcp6-lease-internal.h" +#include "dhcp6-protocol.h" +#include "dns-domain.h" +#include "memory-util.h" +#include "sparse-endian.h" +#include "strv.h" +#include "unaligned.h" + +typedef struct DHCP6StatusOption { + struct DHCP6Option option; + be16_t status; + char msg[]; +} _packed_ DHCP6StatusOption; + +typedef struct DHCP6AddressOption { + struct DHCP6Option option; + struct iaaddr iaaddr; + uint8_t options[]; +} _packed_ DHCP6AddressOption; + +typedef struct DHCP6PDPrefixOption { + struct DHCP6Option option; + struct iapdprefix iapdprefix; + uint8_t options[]; +} _packed_ DHCP6PDPrefixOption; + +#define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na)) +#define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd)) +#define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta)) + +static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode, size_t optlen) { + DHCP6Option *option; + + assert_return(buf, -EINVAL); + assert_return(*buf, -EINVAL); + assert_return(buflen, -EINVAL); + + option = (DHCP6Option*) *buf; + + if (optlen > 0xffff || *buflen < optlen + offsetof(DHCP6Option, data)) + return -ENOBUFS; + + option->code = htobe16(optcode); + option->len = htobe16(optlen); + + *buf += offsetof(DHCP6Option, data); + *buflen -= offsetof(DHCP6Option, data); + + return 0; +} + +int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, + size_t optlen, const void *optval) { + int r; + + assert_return(optval || optlen == 0, -EINVAL); + + r = option_append_hdr(buf, buflen, code, optlen); + if (r < 0) + return r; + + memcpy_safe(*buf, optval, optlen); + + *buf += optlen; + *buflen -= optlen; + + return 0; +} + +int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options) { + sd_dhcp6_option *options; + int r; + + assert(buf); + assert(*buf); + assert(buflen); + assert(vendor_options); + + ORDERED_HASHMAP_FOREACH(options, vendor_options) { + _cleanup_free_ uint8_t *p = NULL; + size_t total; + + total = 4 + 2 + 2 + options->length; + + p = malloc(total); + if (!p) + return -ENOMEM; + + unaligned_write_be32(p, options->enterprise_identifier); + unaligned_write_be16(p + 4, options->option); + unaligned_write_be16(p + 6, options->length); + memcpy(p + 8, options->data, options->length); + + r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_OPTS, total, p); + if (r < 0) + return r; + } + + return 0; +} + +int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) { + size_t ia_buflen, ia_addrlen = 0; + struct ia_na ia_na; + struct ia_ta ia_ta; + DHCP6Address *addr; + uint8_t *ia_hdr; + uint16_t len; + void *p; + int r; + + assert_return(buf, -EINVAL); + assert_return(*buf, -EINVAL); + assert_return(buflen, -EINVAL); + assert_return(ia, -EINVAL); + + /* client should not send set T1 and T2. See, RFC 8415, and issue #18090. */ + + switch (ia->type) { + case SD_DHCP6_OPTION_IA_NA: + len = DHCP6_OPTION_IA_NA_LEN; + ia_na = (struct ia_na) { + .id = ia->ia_na.id, + }; + p = &ia_na; + break; + + case SD_DHCP6_OPTION_IA_TA: + len = DHCP6_OPTION_IA_TA_LEN; + ia_ta = (struct ia_ta) { + .id = ia->ia_ta.id, + }; + p = &ia_ta; + break; + + default: + return -EINVAL; + } + + if (*buflen < offsetof(DHCP6Option, data) + len) + return -ENOBUFS; + + ia_hdr = *buf; + ia_buflen = *buflen; + + *buf += offsetof(DHCP6Option, data); + *buflen -= offsetof(DHCP6Option, data); + + memcpy(*buf, p, len); + + *buf += len; + *buflen -= len; + + LIST_FOREACH(addresses, addr, ia->addresses) { + struct iaaddr a = { + .address = addr->iaaddr.address, + }; + + r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, sizeof(struct iaaddr)); + if (r < 0) + return r; + + memcpy(*buf, &a, sizeof(struct iaaddr)); + + *buf += sizeof(struct iaaddr); + *buflen -= sizeof(struct iaaddr); + + ia_addrlen += offsetof(DHCP6Option, data) + sizeof(struct iaaddr); + } + + return option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen); +} + +static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const DHCP6Address *prefix) { + struct iapdprefix p; + int r; + + assert(buf); + assert(*buf); + assert(buflen); + assert(prefix); + + if (prefix->iapdprefix.prefixlen == 0) + return -EINVAL; + + /* Do not append T1 and T2. */ + + p = (struct iapdprefix) { + .prefixlen = prefix->iapdprefix.prefixlen, + .address = prefix->iapdprefix.address, + }; + + r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IA_PD_PREFIX, sizeof(struct iapdprefix)); + if (r < 0) + return r; + + memcpy(*buf, &p, sizeof(struct iapdprefix)); + + *buf += sizeof(struct iapdprefix); + *buflen -= sizeof(struct iapdprefix); + + return offsetof(DHCP6Option, data) + sizeof(struct iapdprefix); +} + +int dhcp6_option_append_pd(uint8_t **buf, size_t *buflen, const DHCP6IA *pd, const DHCP6Address *hint_pd_prefix) { + struct ia_pd ia_pd; + size_t len, pd_buflen; + uint8_t *pd_hdr; + int r; + + assert_return(buf, -EINVAL); + assert_return(*buf, -EINVAL); + assert_return(buflen, -EINVAL); + assert_return(pd, -EINVAL); + assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL); + + /* Do not set T1 and T2. */ + ia_pd = (struct ia_pd) { + .id = pd->ia_pd.id, + }; + len = sizeof(struct ia_pd); + + if (*buflen < offsetof(DHCP6Option, data) + len) + return -ENOBUFS; + + pd_hdr = *buf; + pd_buflen = *buflen; + + /* The header will be written at the end of this function. */ + *buf += offsetof(DHCP6Option, data); + *buflen -= offsetof(DHCP6Option, data); + + memcpy(*buf, &ia_pd, len); + + *buf += sizeof(struct ia_pd); + *buflen -= sizeof(struct ia_pd); + + DHCP6Address *prefix; + LIST_FOREACH(addresses, prefix, pd->addresses) { + r = option_append_pd_prefix(buf, buflen, prefix); + if (r < 0) + return r; + + len += r; + } + + if (hint_pd_prefix && hint_pd_prefix->iapdprefix.prefixlen > 0) { + r = option_append_pd_prefix(buf, buflen, hint_pd_prefix); + if (r < 0) + return r; + + len += r; + } + + return option_append_hdr(&pd_hdr, &pd_buflen, pd->type, len); +} + +int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) { + uint8_t buffer[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX]; + int r; + + assert_return(buf && *buf && buflen && fqdn, -EINVAL); + + buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */ + + /* Store domain name after flags field */ + r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1, false); + if (r <= 0) + return r; + + /* + * According to RFC 4704, chapter 4.2 only add terminating zero-length + * label in case a FQDN is provided. Since dns_name_to_wire_format + * always adds terminating zero-length label remove if only a hostname + * is provided. + */ + if (dns_name_is_single_label(fqdn)) + r--; + + r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_FQDN, 1 + r, buffer); + + return r; +} + +int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class) { + _cleanup_free_ uint8_t *p = NULL; + size_t total = 0, offset = 0; + char * const *s; + + assert(buf); + assert(*buf); + assert(buflen); + assert(!strv_isempty(user_class)); + + STRV_FOREACH(s, user_class) { + size_t len = strlen(*s); + uint8_t *q; + + if (len > 0xffff || len == 0) + return -EINVAL; + q = realloc(p, total + len + 2); + if (!q) + return -ENOMEM; + + p = q; + + unaligned_write_be16(&p[offset], len); + memcpy(&p[offset + 2], *s, len); + + offset += 2 + len; + total += 2 + len; + } + + return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_USER_CLASS, total, p); +} + +int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const *vendor_class) { + _cleanup_free_ uint8_t *p = NULL; + uint32_t enterprise_identifier; + size_t total, offset; + char * const *s; + + assert(buf); + assert(*buf); + assert(buflen); + assert(!strv_isempty(vendor_class)); + + enterprise_identifier = htobe32(SYSTEMD_PEN); + + p = memdup(&enterprise_identifier, sizeof(enterprise_identifier)); + if (!p) + return -ENOMEM; + + total = sizeof(enterprise_identifier); + offset = total; + + STRV_FOREACH(s, vendor_class) { + size_t len = strlen(*s); + uint8_t *q; + + if (len > UINT16_MAX || len == 0) + return -EINVAL; + + q = realloc(p, total + len + 2); + if (!q) + return -ENOMEM; + + p = q; + + unaligned_write_be16(&p[offset], len); + memcpy(&p[offset + 2], *s, len); + + offset += 2 + len; + total += 2 + len; + } + + return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_CLASS, total, p); +} + +static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) { + DHCP6Option *option = (DHCP6Option*) *buf; + uint16_t len; + + assert_return(buf, -EINVAL); + assert_return(optcode, -EINVAL); + assert_return(optlen, -EINVAL); + + if (*buflen < offsetof(DHCP6Option, data)) + return -ENOMSG; + + len = be16toh(option->len); + + if (len > *buflen) + return -ENOMSG; + + *optcode = be16toh(option->code); + *optlen = len; + + *buf += 4; + *buflen -= 4; + + return 0; +} + +int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, + size_t *optlen, uint8_t **optvalue) { + int r; + + assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL); + + r = option_parse_hdr(buf, buflen, optcode, optlen); + if (r < 0) + return r; + + if (*optlen > *buflen) + return -ENOBUFS; + + *optvalue = *buf; + *buflen -= *optlen; + *buf += *optlen; + + return 0; +} + +int dhcp6_option_parse_status(DHCP6Option *option, size_t len) { + DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option; + + if (len < sizeof(DHCP6StatusOption) || + be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(DHCP6StatusOption)) + return -ENOBUFS; + + return be16toh(statusopt->status); +} + +static int dhcp6_option_parse_address(sd_dhcp6_client *client, DHCP6Option *option, DHCP6IA *ia, uint32_t *ret_lifetime_valid) { + DHCP6AddressOption *addr_option = (DHCP6AddressOption *)option; + DHCP6Address *addr; + uint32_t lt_valid, lt_pref; + int r; + + if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*addr_option)) + return -ENOBUFS; + + lt_valid = be32toh(addr_option->iaaddr.lifetime_valid); + lt_pref = be32toh(addr_option->iaaddr.lifetime_preferred); + + if (lt_valid == 0 || lt_pref > lt_valid) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Valid lifetime of an IA address is zero or " + "preferred lifetime %"PRIu32" > valid lifetime %"PRIu32, + lt_pref, lt_valid); + + if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*addr_option)) { + r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*addr_option)); + if (r < 0) + return r; + if (r > 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Non-zero status code '%s' for address is received", + dhcp6_message_status_to_string(r)); + } + + addr = new0(DHCP6Address, 1); + if (!addr) + return -ENOMEM; + + LIST_INIT(addresses, addr); + memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr)); + + LIST_PREPEND(addresses, ia->addresses, addr); + + *ret_lifetime_valid = be32toh(addr->iaaddr.lifetime_valid); + + return 0; +} + +static int dhcp6_option_parse_pdprefix(sd_dhcp6_client *client, DHCP6Option *option, DHCP6IA *ia, uint32_t *ret_lifetime_valid) { + DHCP6PDPrefixOption *pdprefix_option = (DHCP6PDPrefixOption *)option; + DHCP6Address *prefix; + uint32_t lt_valid, lt_pref; + int r; + + if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*pdprefix_option)) + return -ENOBUFS; + + lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid); + lt_pref = be32toh(pdprefix_option->iapdprefix.lifetime_preferred); + + if (lt_valid == 0 || lt_pref > lt_valid) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Valid lifetieme of a PD prefix is zero or " + "preferred lifetime %"PRIu32" > valid lifetime %"PRIu32, + lt_pref, lt_valid); + + if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*pdprefix_option)) { + r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*pdprefix_option)); + if (r < 0) + return r; + if (r > 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Non-zero status code '%s' for PD prefix is received", + dhcp6_message_status_to_string(r)); + } + + prefix = new0(DHCP6Address, 1); + if (!prefix) + return -ENOMEM; + + LIST_INIT(addresses, prefix); + memcpy(&prefix->iapdprefix, option->data, sizeof(prefix->iapdprefix)); + + LIST_PREPEND(addresses, ia->addresses, prefix); + + *ret_lifetime_valid = be32toh(prefix->iapdprefix.lifetime_valid); + + return 0; +} + +int dhcp6_option_parse_ia(sd_dhcp6_client *client, DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_status_code) { + uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX; + uint16_t iatype, optlen; + size_t iaaddr_offset; + int r = 0, status; + size_t i, len; + uint16_t opt; + + assert_return(ia, -EINVAL); + assert_return(!ia->addresses, -EINVAL); + + iatype = be16toh(iaoption->code); + len = be16toh(iaoption->len); + + switch (iatype) { + case SD_DHCP6_OPTION_IA_NA: + + if (len < DHCP6_OPTION_IA_NA_LEN) + return -ENOBUFS; + + iaaddr_offset = DHCP6_OPTION_IA_NA_LEN; + memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na)); + + lt_t1 = be32toh(ia->ia_na.lifetime_t1); + lt_t2 = be32toh(ia->ia_na.lifetime_t2); + + if (lt_t1 > lt_t2) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "IA NA T1 %"PRIu32"sec > T2 %"PRIu32"sec", + lt_t1, lt_t2); + + break; + + case SD_DHCP6_OPTION_IA_PD: + + if (len < sizeof(ia->ia_pd)) + return -ENOBUFS; + + iaaddr_offset = sizeof(ia->ia_pd); + memcpy(&ia->ia_pd, iaoption->data, sizeof(ia->ia_pd)); + + lt_t1 = be32toh(ia->ia_pd.lifetime_t1); + lt_t2 = be32toh(ia->ia_pd.lifetime_t2); + + if (lt_t1 > lt_t2) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "IA PD T1 %"PRIu32"sec > T2 %"PRIu32"sec", + lt_t1, lt_t2); + + break; + + case SD_DHCP6_OPTION_IA_TA: + if (len < DHCP6_OPTION_IA_TA_LEN) + return -ENOBUFS; + + iaaddr_offset = DHCP6_OPTION_IA_TA_LEN; + memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta)); + + break; + + default: + return -ENOMSG; + } + + ia->type = iatype; + i = iaaddr_offset; + + while (i < len) { + DHCP6Option *option = (DHCP6Option *)&iaoption->data[i]; + + if (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len)) + return -ENOBUFS; + + opt = be16toh(option->code); + optlen = be16toh(option->len); + + switch (opt) { + case SD_DHCP6_OPTION_IAADDR: + + if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "IA Address option not in IA NA or TA option"); + + r = dhcp6_option_parse_address(client, option, ia, <_valid); + if (r < 0 && r != -EINVAL) + return r; + if (r >= 0 && lt_valid < lt_min) + lt_min = lt_valid; + + break; + + case SD_DHCP6_OPTION_IA_PD_PREFIX: + + if (ia->type != SD_DHCP6_OPTION_IA_PD) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "IA PD Prefix option not in IA PD option"); + + r = dhcp6_option_parse_pdprefix(client, option, ia, <_valid); + if (r < 0 && r != -EINVAL) + return r; + if (r >= 0 && lt_valid < lt_min) + lt_min = lt_valid; + + break; + + case SD_DHCP6_OPTION_STATUS_CODE: + + status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data)); + if (status < 0) + return status; + + if (status > 0) { + if (ret_status_code) + *ret_status_code = status; + + log_dhcp6_client(client, "IA status %s", + dhcp6_message_status_to_string(status)); + + return 0; + } + + break; + + default: + log_dhcp6_client(client, "Unknown IA option %d", opt); + break; + } + + i += sizeof(*option) + optlen; + } + + switch(iatype) { + case SD_DHCP6_OPTION_IA_NA: + if (ia->ia_na.lifetime_t1 == 0 && ia->ia_na.lifetime_t2 == 0 && lt_min != UINT32_MAX) { + lt_t1 = lt_min / 2; + lt_t2 = lt_min / 10 * 8; + ia->ia_na.lifetime_t1 = htobe32(lt_t1); + ia->ia_na.lifetime_t2 = htobe32(lt_t2); + + log_dhcp6_client(client, "Computed IA NA T1 %"PRIu32"sec and T2 %"PRIu32"sec as both were zero", + lt_t1, lt_t2); + } + + break; + + case SD_DHCP6_OPTION_IA_PD: + if (ia->ia_pd.lifetime_t1 == 0 && ia->ia_pd.lifetime_t2 == 0 && lt_min != UINT32_MAX) { + lt_t1 = lt_min / 2; + lt_t2 = lt_min / 10 * 8; + ia->ia_pd.lifetime_t1 = htobe32(lt_t1); + ia->ia_pd.lifetime_t2 = htobe32(lt_t2); + + log_dhcp6_client(client, "Computed IA PD T1 %"PRIu32"sec and T2 %"PRIu32"sec as both were zero", + lt_t1, lt_t2); + } + + break; + + default: + break; + } + + if (ret_status_code) + *ret_status_code = 0; + + return 1; +} + +int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen, + struct in6_addr **addrs, size_t count) { + + if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0) + return -EINVAL; + + if (!GREEDY_REALLOC(*addrs, count * sizeof(struct in6_addr) + optlen)) + return -ENOMEM; + + memcpy(*addrs + count, optval, optlen); + + count += optlen / sizeof(struct in6_addr); + + return count; +} + +static int parse_domain(const uint8_t **data, uint16_t *len, char **out_domain) { + _cleanup_free_ char *ret = NULL; + const uint8_t *optval = *data; + uint16_t optlen = *len; + bool first = true; + size_t n = 0; + int r; + + if (optlen <= 1) + return -ENODATA; + + for (;;) { + const char *label; + uint8_t c; + + if (optlen == 0) + break; + + c = *optval; + optval++; + optlen--; + + if (c == 0) + /* End label */ + break; + if (c > 63) + return -EBADMSG; + if (c > optlen) + return -EMSGSIZE; + + /* Literal label */ + label = (const char *)optval; + optval += c; + optlen -= c; + + if (!GREEDY_REALLOC(ret, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + if (first) + first = false; + else + ret[n++] = '.'; + + r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + } + + if (n) { + if (!GREEDY_REALLOC(ret, n + 1)) + return -ENOMEM; + ret[n] = 0; + } + + *out_domain = TAKE_PTR(ret); + *data = optval; + *len = optlen; + + return n; +} + +int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str) { + _cleanup_free_ char *domain = NULL; + int r; + + r = parse_domain(&optval, &optlen, &domain); + if (r < 0) + return r; + if (r == 0) + return -ENODATA; + if (optlen != 0) + return -EINVAL; + + *str = TAKE_PTR(domain); + return 0; +} + +int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen, char ***str_arr) { + size_t idx = 0; + _cleanup_strv_free_ char **names = NULL; + int r; + + if (optlen <= 1) + return -ENODATA; + if (optval[optlen - 1] != '\0') + return -EINVAL; + + while (optlen > 0) { + _cleanup_free_ char *ret = NULL; + + r = parse_domain(&optval, &optlen, &ret); + if (r < 0) + return r; + if (r == 0) + continue; + + r = strv_extend(&names, ret); + if (r < 0) + return r; + + idx++; + } + + *str_arr = TAKE_PTR(names); + + return idx; +} + +static sd_dhcp6_option* dhcp6_option_free(sd_dhcp6_option *i) { + if (!i) + return NULL; + + free(i->data); + return mfree(i); +} + +int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret) { + assert_return(ret, -EINVAL); + assert_return(length == 0 || data, -EINVAL); + + _cleanup_free_ void *q = memdup(data, length); + if (!q) + return -ENOMEM; + + sd_dhcp6_option *p = new(sd_dhcp6_option, 1); + if (!p) + return -ENOMEM; + + *p = (sd_dhcp6_option) { + .n_ref = 1, + .option = option, + .enterprise_identifier = enterprise_identifier, + .length = length, + .data = TAKE_PTR(q), + }; + + *ret = p; + return 0; +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option, sd_dhcp6_option, dhcp6_option_free); +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + dhcp6_option_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + sd_dhcp6_option, + sd_dhcp6_option_unref); diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h new file mode 100644 index 00000000..c7003638 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2014 Intel Corporation. All rights reserved. +***/ + +#include <netinet/ip6.h> +#include <netinet/udp.h> + +#include "macro.h" +#include "sparse-endian.h" + +struct DHCP6Message { + union { + struct { + uint8_t type; + uint8_t _pad[3]; + } _packed_; + be32_t transaction_id; + }; + uint8_t options[]; +} _packed_; + +typedef struct DHCP6Message DHCP6Message; + +#define DHCP6_MIN_OPTIONS_SIZE \ + 1280 - sizeof(struct ip6_hdr) - sizeof(struct udphdr) + +#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \ + { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } } + +enum { + DHCP6_PORT_SERVER = 547, + DHCP6_PORT_CLIENT = 546, +}; + +#define DHCP6_INF_TIMEOUT 1 * USEC_PER_SEC +#define DHCP6_INF_MAX_RT 120 * USEC_PER_SEC +#define DHCP6_SOL_MAX_DELAY 1 * USEC_PER_SEC +#define DHCP6_SOL_TIMEOUT 1 * USEC_PER_SEC +#define DHCP6_SOL_MAX_RT 120 * USEC_PER_SEC +#define DHCP6_REQ_TIMEOUT 1 * USEC_PER_SEC +#define DHCP6_REQ_MAX_RT 120 * USEC_PER_SEC +#define DHCP6_REQ_MAX_RC 10 +#define DHCP6_REN_TIMEOUT 10 * USEC_PER_SEC +#define DHCP6_REN_MAX_RT 600 * USEC_PER_SEC +#define DHCP6_REB_TIMEOUT 10 * USEC_PER_SEC +#define DHCP6_REB_MAX_RT 600 * USEC_PER_SEC + +enum DHCP6State { + DHCP6_STATE_STOPPED = 0, + DHCP6_STATE_INFORMATION_REQUEST = 1, + DHCP6_STATE_SOLICITATION = 2, + DHCP6_STATE_REQUEST = 3, + DHCP6_STATE_BOUND = 4, + DHCP6_STATE_RENEW = 5, + DHCP6_STATE_REBIND = 6, +}; + +enum { + DHCP6_SOLICIT = 1, + DHCP6_ADVERTISE = 2, + DHCP6_REQUEST = 3, + DHCP6_CONFIRM = 4, + DHCP6_RENEW = 5, + DHCP6_REBIND = 6, + DHCP6_REPLY = 7, + DHCP6_RELEASE = 8, + DHCP6_DECLINE = 9, + DHCP6_RECONFIGURE = 10, + DHCP6_INFORMATION_REQUEST = 11, + DHCP6_RELAY_FORW = 12, + DHCP6_RELAY_REPL = 13, + _DHCP6_MESSAGE_MAX = 14, +}; + +enum { + DHCP6_NTP_SUBOPTION_SRV_ADDR = 1, + DHCP6_NTP_SUBOPTION_MC_ADDR = 2, + DHCP6_NTP_SUBOPTION_SRV_FQDN = 3, +}; + +/* + * RFC 8415, RFC 5007 and RFC 7653 status codes: + * https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-5 + */ +enum { + DHCP6_STATUS_SUCCESS = 0, + DHCP6_STATUS_UNSPEC_FAIL = 1, + DHCP6_STATUS_NO_ADDRS_AVAIL = 2, + DHCP6_STATUS_NO_BINDING = 3, + DHCP6_STATUS_NOT_ON_LINK = 4, + DHCP6_STATUS_USE_MULTICAST = 5, + DHCP6_STATUS_NO_PREFIX_AVAIL = 6, + DHCP6_STATUS_UNKNOWN_QUERY_TYPE = 7, + DHCP6_STATUS_MALFORMED_QUERY = 8, + DHCP6_STATUS_NOT_CONFIGURED = 9, + DHCP6_STATUS_NOT_ALLOWED = 10, + DHCP6_STATUS_QUERY_TERMINATED = 11, + DHCP6_STATUS_DATA_MISSING = 12, + DHCP6_STATUS_CATCHUP_COMPLETE = 13, + DHCP6_STATUS_NOT_SUPPORTED = 14, + DHCP6_STATUS_TLS_CONNECTION_REFUSED = 15, + DHCP6_STATUS_ADDRESS_IN_USE = 16, + DHCP6_STATUS_CONFIGURATION_CONFLICT = 17, + DHCP6_STATUS_MISSING_BINDING_INFORMATION = 18, + DHCP6_STATUS_OUTDATED_BINDING_INFORMATION = 19, + DHCP6_STATUS_SERVER_SHUTTING_DOWN = 20, + DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED = 21, + DHCP6_STATUS_EXCESSIVE_TIME_SKEW = 22, + _DHCP6_STATUS_MAX = 23, +}; + +enum { + DHCP6_FQDN_FLAG_S = (1 << 0), + DHCP6_FQDN_FLAG_O = (1 << 1), + DHCP6_FQDN_FLAG_N = (1 << 2), +}; diff --git a/src/libnm-systemd-core/src/libsystemd-network/lldp-internal.h b/src/libnm-systemd-core/src/libsystemd-network/lldp-internal.h new file mode 100644 index 00000000..cf0578c5 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/lldp-internal.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-event.h" +#include "sd-lldp.h" + +#include "hashmap.h" +#include "log-link.h" +#include "prioq.h" + +struct sd_lldp { + unsigned n_ref; + + int ifindex; + char *ifname; + int fd; + + sd_event *event; + int64_t event_priority; + sd_event_source *io_event_source; + sd_event_source *timer_event_source; + + Prioq *neighbor_by_expiry; + Hashmap *neighbor_by_id; + + uint64_t neighbors_max; + + sd_lldp_callback_t callback; + void *userdata; + + uint16_t capability_mask; + + struct ether_addr filter_address; +}; + +const char* lldp_event_to_string(sd_lldp_event_t e) _const_; +sd_lldp_event_t lldp_event_from_string(const char *s) _pure_; + +#define log_lldp_errno(lldp, error, fmt, ...) \ + log_interface_prefix_full_errno( \ + "LLDP: ", \ + sd_lldp_get_ifname(lldp), \ + error, fmt, ##__VA_ARGS__) +#define log_lldp(lldp, fmt, ...) \ + log_interface_prefix_full_errno_zerook( \ + "LLDP: ", \ + sd_lldp_get_ifname(lldp), \ + 0, fmt, ##__VA_ARGS__) diff --git a/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.c b/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.c new file mode 100644 index 00000000..1b2108d8 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.c @@ -0,0 +1,781 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include "alloc-util.h" +#include "escape.h" +#include "ether-addr-util.h" +#include "hexdecoct.h" +#include "in-addr-util.h" +#include "lldp-internal.h" +#include "lldp-neighbor.h" +#include "memory-util.h" +#include "missing_network.h" +#include "unaligned.h" + +static void lldp_neighbor_id_hash_func(const LLDPNeighborID *id, struct siphash *state) { + siphash24_compress(id->chassis_id, id->chassis_id_size, state); + siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state); + siphash24_compress(id->port_id, id->port_id_size, state); + siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state); +} + +int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y) { + return memcmp_nn(x->chassis_id, x->chassis_id_size, y->chassis_id, y->chassis_id_size) + ?: memcmp_nn(x->port_id, x->port_id_size, y->port_id, y->port_id_size); +} + +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(lldp_neighbor_hash_ops, LLDPNeighborID, lldp_neighbor_id_hash_func, lldp_neighbor_id_compare_func, + sd_lldp_neighbor, lldp_neighbor_unlink); + +int lldp_neighbor_prioq_compare_func(const void *a, const void *b) { + const sd_lldp_neighbor *x = a, *y = b; + + return CMP(x->until, y->until); +} + +_public_ sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n) { + if (!n) + return NULL; + + assert(n->n_ref > 0 || n->lldp); + n->n_ref++; + + return n; +} + +static void lldp_neighbor_free(sd_lldp_neighbor *n) { + assert(n); + + free(n->id.port_id); + free(n->id.chassis_id); + free(n->port_description); + free(n->system_name); + free(n->system_description); + free(n->mud_url); + free(n->chassis_id_as_string); + free(n->port_id_as_string); + free(n); +} + +_public_ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n) { + + /* Drops one reference from the neighbor. Note that the object is not freed unless it is already unlinked from + * the sd_lldp object. */ + + if (!n) + return NULL; + + assert(n->n_ref > 0); + n->n_ref--; + + if (n->n_ref <= 0 && !n->lldp) + lldp_neighbor_free(n); + + return NULL; +} + +sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n) { + + /* Removes the neighbor object from the LLDP object, and frees it if it also has no other reference. */ + + if (!n) + return NULL; + + if (!n->lldp) + return NULL; + + /* Only remove the neighbor object from the hash table if it's in there, don't complain if it isn't. This is + * because we are used as destructor call for hashmap_clear() and thus sometimes are called to de-register + * ourselves from the hashtable and sometimes are called after we already are de-registered. */ + + (void) hashmap_remove_value(n->lldp->neighbor_by_id, &n->id, n); + + assert_se(prioq_remove(n->lldp->neighbor_by_expiry, n, &n->prioq_idx) >= 0); + + n->lldp = NULL; + + if (n->n_ref <= 0) + lldp_neighbor_free(n); + + return NULL; +} + +sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size) { + sd_lldp_neighbor *n; + + n = malloc0(ALIGN(sizeof(sd_lldp_neighbor)) + raw_size); + if (!n) + return NULL; + + n->raw_size = raw_size; + n->n_ref = 1; + + return n; +} + +static int parse_string(sd_lldp *lldp, char **s, const void *q, size_t n) { + const char *p = q; + char *k; + + assert(s); + assert(p || n == 0); + + if (*s) { + log_lldp(lldp, "Found duplicate string, ignoring field."); + return 0; + } + + /* Strip trailing NULs, just to be nice */ + while (n > 0 && p[n-1] == 0) + n--; + + if (n <= 0) /* Ignore empty strings */ + return 0; + + /* Look for inner NULs */ + if (memchr(p, 0, n)) { + log_lldp(lldp, "Found inner NUL in string, ignoring field."); + return 0; + } + + /* Let's escape weird chars, for security reasons */ + k = cescape_length(p, n); + if (!k) + return log_oom_debug(); + + free(*s); + *s = k; + + return 1; +} + +int lldp_neighbor_parse(sd_lldp_neighbor *n) { + struct ether_header h; + const uint8_t *p; + size_t left; + int r; + + assert(n); + + if (n->raw_size < sizeof(struct ether_header)) + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "Received truncated packet, ignoring."); + + memcpy(&h, LLDP_NEIGHBOR_RAW(n), sizeof(h)); + + if (h.ether_type != htobe16(ETHERTYPE_LLDP)) + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "Received packet with wrong type, ignoring."); + + if (h.ether_dhost[0] != 0x01 || + h.ether_dhost[1] != 0x80 || + h.ether_dhost[2] != 0xc2 || + h.ether_dhost[3] != 0x00 || + h.ether_dhost[4] != 0x00 || + !IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e)) + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "Received packet with wrong destination address, ignoring."); + + memcpy(&n->source_address, h.ether_shost, sizeof(struct ether_addr)); + memcpy(&n->destination_address, h.ether_dhost, sizeof(struct ether_addr)); + + p = (const uint8_t*) LLDP_NEIGHBOR_RAW(n) + sizeof(struct ether_header); + left = n->raw_size - sizeof(struct ether_header); + + for (;;) { + uint8_t type; + uint16_t length; + + if (left < 2) + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "TLV lacks header, ignoring."); + + type = p[0] >> 1; + length = p[1] + (((uint16_t) (p[0] & 1)) << 8); + p += 2, left -= 2; + + if (left < length) + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "TLV truncated, ignoring datagram."); + + switch (type) { + + case SD_LLDP_TYPE_END: + if (length != 0) + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "End marker TLV not zero-sized, ignoring datagram."); + + /* Note that after processing the SD_LLDP_TYPE_END left could still be > 0 + * as the message may contain padding (see IEEE 802.1AB-2016, sec. 8.5.12) */ + + goto end_marker; + + case SD_LLDP_TYPE_CHASSIS_ID: + if (length < 2 || length > 256) + /* includes the chassis subtype, hence one extra byte */ + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "Chassis ID field size out of range, ignoring datagram."); + + if (n->id.chassis_id) + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "Duplicate chassis ID field, ignoring datagram."); + + n->id.chassis_id = memdup(p, length); + if (!n->id.chassis_id) + return log_oom_debug(); + + n->id.chassis_id_size = length; + break; + + case SD_LLDP_TYPE_PORT_ID: + if (length < 2 || length > 256) + /* includes the port subtype, hence one extra byte */ + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "Port ID field size out of range, ignoring datagram."); + + if (n->id.port_id) + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "Duplicate port ID field, ignoring datagram."); + + n->id.port_id = memdup(p, length); + if (!n->id.port_id) + return log_oom_debug(); + + n->id.port_id_size = length; + break; + + case SD_LLDP_TYPE_TTL: + if (length != 2) + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "TTL field has wrong size, ignoring datagram."); + + if (n->has_ttl) + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "Duplicate TTL field, ignoring datagram."); + + n->ttl = unaligned_read_be16(p); + n->has_ttl = true; + break; + + case SD_LLDP_TYPE_PORT_DESCRIPTION: + r = parse_string(n->lldp, &n->port_description, p, length); + if (r < 0) + return r; + break; + + case SD_LLDP_TYPE_SYSTEM_NAME: + r = parse_string(n->lldp, &n->system_name, p, length); + if (r < 0) + return r; + break; + + case SD_LLDP_TYPE_SYSTEM_DESCRIPTION: + r = parse_string(n->lldp, &n->system_description, p, length); + if (r < 0) + return r; + break; + + case SD_LLDP_TYPE_SYSTEM_CAPABILITIES: + if (length != 4) + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "System capabilities field has wrong size."); + + n->system_capabilities = unaligned_read_be16(p); + n->enabled_capabilities = unaligned_read_be16(p + 2); + n->has_capabilities = true; + break; + + case SD_LLDP_TYPE_PRIVATE: + if (length < 4) + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "Found private TLV that is too short, ignoring."); + + /* RFC 8520: MUD URL */ + if (memcmp(p, SD_LLDP_OUI_MUD, sizeof(SD_LLDP_OUI_MUD)) == 0 && + p[sizeof(SD_LLDP_OUI_MUD)] == SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION) { + r = parse_string(n->lldp, &n->mud_url, p + sizeof(SD_LLDP_OUI_MUD) + 1, + length - 1 - sizeof(SD_LLDP_OUI_MUD)); + if (r < 0) + return r; + } + break; + } + + p += length, left -= length; + } + +end_marker: + if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl) + return log_lldp_errno(n->lldp, SYNTHETIC_ERRNO(EBADMSG), + "One or more mandatory TLV missing in datagram. Ignoring."); + + n->rindex = sizeof(struct ether_header); + + return 0; +} + +void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) { + assert(n); + + if (n->ttl > 0) { + usec_t base; + + /* Use the packet's timestamp if there is one known */ + base = triple_timestamp_by_clock(&n->timestamp, clock_boottime_or_monotonic()); + if (base <= 0 || base == USEC_INFINITY) + base = now(clock_boottime_or_monotonic()); /* Otherwise, take the current time */ + + n->until = usec_add(base, n->ttl * USEC_PER_SEC); + } else + n->until = 0; + + if (n->lldp) + prioq_reshuffle(n->lldp->neighbor_by_expiry, n, &n->prioq_idx); +} + +bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b) { + if (a == b) + return true; + + if (!a || !b) + return false; + + if (a->raw_size != b->raw_size) + return false; + + return memcmp(LLDP_NEIGHBOR_RAW(a), LLDP_NEIGHBOR_RAW(b), a->raw_size) == 0; +} + +_public_ int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address) { + assert_return(n, -EINVAL); + assert_return(address, -EINVAL); + + *address = n->source_address; + return 0; +} + +_public_ int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address) { + assert_return(n, -EINVAL); + assert_return(address, -EINVAL); + + *address = n->destination_address; + return 0; +} + +_public_ int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + *ret = LLDP_NEIGHBOR_RAW(n); + *size = n->raw_size; + + return 0; +} + +_public_ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) { + assert_return(n, -EINVAL); + assert_return(type, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + assert(n->id.chassis_id_size > 0); + + *type = *(uint8_t*) n->id.chassis_id; + *ret = (uint8_t*) n->id.chassis_id + 1; + *size = n->id.chassis_id_size - 1; + + return 0; +} + +static int format_mac_address(const void *data, size_t sz, char **ret) { + struct ether_addr a; + char *k; + + assert(data || sz <= 0); + + if (sz != 7) + return 0; + + memcpy(&a, (uint8_t*) data + 1, sizeof(a)); + + k = new(char, ETHER_ADDR_TO_STRING_MAX); + if (!k) + return -ENOMEM; + + *ret = ether_addr_to_string(&a, k); + return 1; +} + +static int format_network_address(const void *data, size_t sz, char **ret) { + union in_addr_union a; + int family, r; + + if (sz == 6 && ((uint8_t*) data)[1] == 1) { + memcpy(&a.in, (uint8_t*) data + 2, sizeof(a.in)); + family = AF_INET; + } else if (sz == 18 && ((uint8_t*) data)[1] == 2) { + memcpy(&a.in6, (uint8_t*) data + 2, sizeof(a.in6)); + family = AF_INET6; + } else + return 0; + + r = in_addr_to_string(family, &a, ret); + if (r < 0) + return r; + return 1; +} + +_public_ int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret) { + char *k; + int r; + + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (n->chassis_id_as_string) { + *ret = n->chassis_id_as_string; + return 0; + } + + assert(n->id.chassis_id_size > 0); + + switch (*(uint8_t*) n->id.chassis_id) { + + case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT: + case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS: + case SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT: + case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME: + case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED: + k = cescape_length((char*) n->id.chassis_id + 1, n->id.chassis_id_size - 1); + if (!k) + return -ENOMEM; + + goto done; + + case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS: + r = format_mac_address(n->id.chassis_id, n->id.chassis_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + + case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS: + r = format_network_address(n->id.chassis_id, n->id.chassis_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + } + + /* Generic fallback */ + k = hexmem(n->id.chassis_id, n->id.chassis_id_size); + if (!k) + return -ENOMEM; + +done: + *ret = n->chassis_id_as_string = k; + return 0; +} + +_public_ int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) { + assert_return(n, -EINVAL); + assert_return(type, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + assert(n->id.port_id_size > 0); + + *type = *(uint8_t*) n->id.port_id; + *ret = (uint8_t*) n->id.port_id + 1; + *size = n->id.port_id_size - 1; + + return 0; +} + +_public_ int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret) { + char *k; + int r; + + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (n->port_id_as_string) { + *ret = n->port_id_as_string; + return 0; + } + + assert(n->id.port_id_size > 0); + + switch (*(uint8_t*) n->id.port_id) { + + case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS: + case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT: + case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME: + case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED: + k = cescape_length((char*) n->id.port_id + 1, n->id.port_id_size - 1); + if (!k) + return -ENOMEM; + + goto done; + + case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS: + r = format_mac_address(n->id.port_id, n->id.port_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + + case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS: + r = format_network_address(n->id.port_id, n->id.port_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + } + + /* Generic fallback */ + k = hexmem(n->id.port_id, n->id.port_id_size); + if (!k) + return -ENOMEM; + +done: + *ret = n->port_id_as_string = k; + return 0; +} + +_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec) { + assert_return(n, -EINVAL); + assert_return(ret_sec, -EINVAL); + + *ret_sec = n->ttl; + return 0; +} + +_public_ int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->system_name) + return -ENODATA; + + *ret = n->system_name; + return 0; +} + +_public_ int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->system_description) + return -ENODATA; + + *ret = n->system_description; + return 0; +} + +_public_ int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->port_description) + return -ENODATA; + + *ret = n->port_description; + return 0; +} + +_public_ int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor *n, const char **ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->mud_url) + return -ENODATA; + + *ret = n->mud_url; + return 0; +} + +_public_ int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->has_capabilities) + return -ENODATA; + + *ret = n->system_capabilities; + return 0; +} + +_public_ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->has_capabilities) + return -ENODATA; + + *ret = n->enabled_capabilities; + return 0; +} + +_public_ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + int r; + + assert_return(ret, -EINVAL); + assert_return(raw || raw_size <= 0, -EINVAL); + + n = lldp_neighbor_new(raw_size); + if (!n) + return -ENOMEM; + + memcpy(LLDP_NEIGHBOR_RAW(n), raw, raw_size); + r = lldp_neighbor_parse(n); + if (r < 0) + return r; + + *ret = TAKE_PTR(n); + + return r; +} + +_public_ int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) { + assert_return(n, -EINVAL); + + assert(n->raw_size >= sizeof(struct ether_header)); + n->rindex = sizeof(struct ether_header); + + return n->rindex < n->raw_size; +} + +_public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) { + size_t length; + + assert_return(n, -EINVAL); + + if (n->rindex == n->raw_size) /* EOF */ + return -ESPIPE; + + if (n->rindex + 2 > n->raw_size) /* Truncated message */ + return -EBADMSG; + + length = LLDP_NEIGHBOR_TLV_LENGTH(n); + if (n->rindex + 2 + length > n->raw_size) + return -EBADMSG; + + n->rindex += 2 + length; + return n->rindex < n->raw_size; +} + +_public_ int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) { + assert_return(n, -EINVAL); + assert_return(type, -EINVAL); + + if (n->rindex == n->raw_size) /* EOF */ + return -ESPIPE; + + if (n->rindex + 2 > n->raw_size) + return -EBADMSG; + + *type = LLDP_NEIGHBOR_TLV_TYPE(n); + return 0; +} + +_public_ int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type) { + uint8_t k; + int r; + + assert_return(n, -EINVAL); + + r = sd_lldp_neighbor_tlv_get_type(n, &k); + if (r < 0) + return r; + + return type == k; +} + +_public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype) { + const uint8_t *d; + size_t length; + int r; + + assert_return(n, -EINVAL); + assert_return(oui, -EINVAL); + assert_return(subtype, -EINVAL); + + r = sd_lldp_neighbor_tlv_is_type(n, SD_LLDP_TYPE_PRIVATE); + if (r < 0) + return r; + if (r == 0) + return -ENXIO; + + length = LLDP_NEIGHBOR_TLV_LENGTH(n); + if (length < 4) + return -EBADMSG; + + if (n->rindex + 2 + length > n->raw_size) + return -EBADMSG; + + d = LLDP_NEIGHBOR_TLV_DATA(n); + memcpy(oui, d, 3); + *subtype = d[3]; + + return 0; +} + +_public_ int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype) { + uint8_t k[3], st; + int r; + + r = sd_lldp_neighbor_tlv_get_oui(n, k, &st); + if (r == -ENXIO) + return 0; + if (r < 0) + return r; + + return memcmp(k, oui, 3) == 0 && st == subtype; +} + +_public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) { + size_t length; + + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + /* Note that this returns the full TLV, including the TLV header */ + + if (n->rindex + 2 > n->raw_size) + return -EBADMSG; + + length = LLDP_NEIGHBOR_TLV_LENGTH(n); + if (n->rindex + 2 + length > n->raw_size) + return -EBADMSG; + + *ret = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex; + *size = length + 2; + + return 0; +} + +_public_ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret) { + assert_return(n, -EINVAL); + assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP); + assert_return(clock_supported(clock), -EOPNOTSUPP); + assert_return(ret, -EINVAL); + + if (!triple_timestamp_is_set(&n->timestamp)) + return -ENODATA; + + *ret = triple_timestamp_by_clock(&n->timestamp, clock); + return 0; +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.h b/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.h new file mode 100644 index 00000000..a5718c8c --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <inttypes.h> +#include <stdbool.h> +#include <sys/types.h> + +#include "sd-lldp.h" + +#include "hash-funcs.h" +#include "lldp-internal.h" +#include "time-util.h" + +typedef struct LLDPNeighborID { + /* The spec calls this an "MSAP identifier" */ + void *chassis_id; + size_t chassis_id_size; + + void *port_id; + size_t port_id_size; +} LLDPNeighborID; + +struct sd_lldp_neighbor { + /* Neighbor objects stay around as long as they are linked into an "sd_lldp" object or n_ref > 0. */ + sd_lldp *lldp; + unsigned n_ref; + + triple_timestamp timestamp; + + usec_t until; + unsigned prioq_idx; + + struct ether_addr source_address; + struct ether_addr destination_address; + + LLDPNeighborID id; + + /* The raw packet size. The data is appended to the object, accessible via LLDP_NEIGHBOR_RAW() */ + size_t raw_size; + + /* The current read index for the iterative TLV interface */ + size_t rindex; + + /* And a couple of fields parsed out. */ + bool has_ttl:1; + bool has_capabilities:1; + bool has_port_vlan_id:1; + + uint16_t ttl; + + uint16_t system_capabilities; + uint16_t enabled_capabilities; + + char *port_description; + char *system_name; + char *system_description; + char *mud_url; + + uint16_t port_vlan_id; + + char *chassis_id_as_string; + char *port_id_as_string; +}; + +static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) { + return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor)); +} + +static inline uint8_t LLDP_NEIGHBOR_TLV_TYPE(const sd_lldp_neighbor *n) { + return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1; +} + +static inline size_t LLDP_NEIGHBOR_TLV_LENGTH(const sd_lldp_neighbor *n) { + uint8_t *p; + + p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex; + return p[1] + (((size_t) (p[0] & 1)) << 8); +} + +static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) { + return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2; +} + +extern const struct hash_ops lldp_neighbor_hash_ops; +int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y); +int lldp_neighbor_prioq_compare_func(const void *a, const void *b); + +sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n); +sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size); +int lldp_neighbor_parse(sd_lldp_neighbor *n); +void lldp_neighbor_start_ttl(sd_lldp_neighbor *n); +bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b); diff --git a/src/libnm-systemd-core/src/libsystemd-network/lldp-network.c b/src/libnm-systemd-core/src/libsystemd-network/lldp-network.c new file mode 100644 index 00000000..e874e863 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/lldp-network.c @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include <linux/filter.h> +#include <netinet/if_ether.h> + +#include "fd-util.h" +#include "lldp-network.h" +#include "missing_network.h" +#include "socket-util.h" + +int lldp_network_bind_raw_socket(int ifindex) { + + static const struct sock_filter filter[] = { + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ethhdr, h_dest)), /* A <- 4 bytes of destination MAC */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0180c200, 1, 0), /* A != 01:80:c2:00 */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_dest) + 4), /* A <- remaining 2 bytes of destination MAC */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0000, 3, 0), /* A != 00:00 */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0003, 2, 0), /* A != 00:03 */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x000e, 1, 0), /* A != 00:0e */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_proto)), /* A <- protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_LLDP, 1, 0), /* A != ETHERTYPE_LLDP */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + BPF_STMT(BPF_RET + BPF_K, UINT32_MAX), /* accept packet */ + }; + + static const struct sock_fprog fprog = { + .len = ELEMENTSOF(filter), + .filter = (struct sock_filter*) filter, + }; + + struct packet_mreq mreq = { + .mr_ifindex = ifindex, + .mr_type = PACKET_MR_MULTICAST, + .mr_alen = ETH_ALEN, + .mr_address = { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x00 } + }; + + union sockaddr_union saddrll = { + .ll.sll_family = AF_PACKET, + .ll.sll_ifindex = ifindex, + }; + + _cleanup_close_ int fd = -1; + int r; + + assert(ifindex > 0); + + fd = socket(AF_PACKET, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, + htobe16(ETHERTYPE_LLDP)); + if (fd < 0) + return -errno; + + r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + if (r < 0) + return -errno; + + mreq.mr_address[ETH_ALEN - 1] = 0x03; + r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + if (r < 0) + return -errno; + + mreq.mr_address[ETH_ALEN - 1] = 0x0E; + r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + if (r < 0) + return -errno; + + r = bind(fd, &saddrll.sa, sizeof(saddrll.ll)); + if (r < 0) + return -errno; + + return TAKE_FD(fd); +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/lldp-network.h b/src/libnm-systemd-core/src/libsystemd-network/lldp-network.h new file mode 100644 index 00000000..bc69b324 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/lldp-network.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-event.h" + +int lldp_network_bind_raw_socket(int ifindex); diff --git a/src/libnm-systemd-core/src/libsystemd-network/network-common.c b/src/libnm-systemd-core/src/libsystemd-network/network-common.c new file mode 100644 index 00000000..b9494c15 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/network-common.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include "format-util.h" +#include "network-common.h" +#include "string-util.h" + +const char *get_ifname(int ifindex, char **ifname) { + char buf[IF_NAMESIZE + 1]; + + assert(ifname); + + /* This sets ifname only when it is not set yet. */ + + if (*ifname) + return *ifname; + + if (ifindex <= 0) + return NULL; + + if (!format_ifname(ifindex, buf)) + return NULL; + + return *ifname = strdup(buf); +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/network-common.h b/src/libnm-systemd-core/src/libsystemd-network/network-common.h new file mode 100644 index 00000000..76a6c4a9 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/network-common.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +const char *get_ifname(int ifindex, char **ifname); diff --git a/src/libnm-systemd-core/src/libsystemd-network/network-internal.c b/src/libnm-systemd-core/src/libsystemd-network/network-internal.c new file mode 100644 index 00000000..60ce47ea --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/network-internal.c @@ -0,0 +1,243 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include <arpa/inet.h> +#include <linux/if.h> +#include <netinet/ether.h> + +#include "sd-ndisc.h" + +#include "alloc-util.h" +#include "dhcp-lease-internal.h" +#include "extract-word.h" +#include "hexdecoct.h" +#include "log.h" +#include "network-internal.h" +#include "parse-util.h" + +size_t serialize_in_addrs(FILE *f, + const struct in_addr *addresses, + size_t size, + bool *with_leading_space, + bool (*predicate)(const struct in_addr *addr)) { + assert(f); + assert(addresses); + + size_t count = 0; + bool _space = false; + if (!with_leading_space) + with_leading_space = &_space; + + for (size_t i = 0; i < size; i++) { + char sbuf[INET_ADDRSTRLEN]; + + if (predicate && !predicate(&addresses[i])) + continue; + + if (*with_leading_space) + fputc(' ', f); + fputs(inet_ntop(AF_INET, &addresses[i], sbuf, sizeof(sbuf)), f); + count++; + *with_leading_space = true; + } + + return count; +} + +int deserialize_in_addrs(struct in_addr **ret, const char *string) { + _cleanup_free_ struct in_addr *addresses = NULL; + int size = 0; + + assert(ret); + assert(string); + + for (;;) { + _cleanup_free_ char *word = NULL; + struct in_addr *new_addresses; + int r; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + new_addresses = reallocarray(addresses, size + 1, sizeof(struct in_addr)); + if (!new_addresses) + return -ENOMEM; + else + addresses = new_addresses; + + r = inet_pton(AF_INET, word, &(addresses[size])); + if (r <= 0) + continue; + + size++; + } + + *ret = size > 0 ? TAKE_PTR(addresses) : NULL; + + return size; +} + +void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, size_t size, bool *with_leading_space) { + assert(f); + assert(addresses); + assert(size); + + bool _space = false; + if (!with_leading_space) + with_leading_space = &_space; + + for (size_t i = 0; i < size; i++) { + char buffer[INET6_ADDRSTRLEN]; + + if (*with_leading_space) + fputc(' ', f); + fputs(inet_ntop(AF_INET6, addresses+i, buffer, sizeof(buffer)), f); + *with_leading_space = true; + } +} + +int deserialize_in6_addrs(struct in6_addr **ret, const char *string) { + _cleanup_free_ struct in6_addr *addresses = NULL; + int size = 0; + + assert(ret); + assert(string); + + for (;;) { + _cleanup_free_ char *word = NULL; + struct in6_addr *new_addresses; + int r; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + new_addresses = reallocarray(addresses, size + 1, sizeof(struct in6_addr)); + if (!new_addresses) + return -ENOMEM; + else + addresses = new_addresses; + + r = inet_pton(AF_INET6, word, &(addresses[size])); + if (r <= 0) + continue; + + size++; + } + + *ret = TAKE_PTR(addresses); + + return size; +} + +void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) { + assert(f); + assert(key); + assert(routes); + assert(size); + + fprintf(f, "%s=", key); + + for (size_t i = 0; i < size; i++) { + char sbuf[INET_ADDRSTRLEN]; + struct in_addr dest, gw; + uint8_t length; + + assert_se(sd_dhcp_route_get_destination(routes[i], &dest) >= 0); + assert_se(sd_dhcp_route_get_gateway(routes[i], &gw) >= 0); + assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &length) >= 0); + + fprintf(f, "%s/%" PRIu8, inet_ntop(AF_INET, &dest, sbuf, sizeof sbuf), length); + fprintf(f, ",%s%s", inet_ntop(AF_INET, &gw, sbuf, sizeof sbuf), i < size - 1 ? " ": ""); + } + + fputs("\n", f); +} + +int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, const char *string) { + _cleanup_free_ struct sd_dhcp_route *routes = NULL; + size_t size = 0; + + assert(ret); + assert(ret_size); + assert(string); + + /* WORD FORMAT: dst_ip/dst_prefixlen,gw_ip */ + for (;;) { + _cleanup_free_ char *word = NULL; + char *tok, *tok_end; + unsigned n; + int r; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + if (!GREEDY_REALLOC(routes, size + 1)) + return -ENOMEM; + + tok = word; + + /* get the subnet */ + tok_end = strchr(tok, '/'); + if (!tok_end) + continue; + *tok_end = '\0'; + + r = inet_aton(tok, &routes[size].dst_addr); + if (r == 0) + continue; + + tok = tok_end + 1; + + /* get the prefixlen */ + tok_end = strchr(tok, ','); + if (!tok_end) + continue; + + *tok_end = '\0'; + + r = safe_atou(tok, &n); + if (r < 0 || n > 32) + continue; + + routes[size].dst_prefixlen = (uint8_t) n; + tok = tok_end + 1; + + /* get the gateway */ + r = inet_aton(tok, &routes[size].gw_addr); + if (r == 0) + continue; + + size++; + } + + *ret_size = size; + *ret = TAKE_PTR(routes); + + return 0; +} + +int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size) { + _cleanup_free_ char *hex_buf = NULL; + + assert(f); + assert(key); + assert(data); + + hex_buf = hexmem(data, size); + if (!hex_buf) + return -ENOMEM; + + fprintf(f, "%s=%s\n", key, hex_buf); + + return 0; +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/network-internal.h b/src/libnm-systemd-core/src/libsystemd-network/network-internal.h new file mode 100644 index 00000000..895a00d0 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/network-internal.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <stdbool.h> +#include <stdio.h> + +#include "sd-dhcp-lease.h" + +size_t serialize_in_addrs(FILE *f, + const struct in_addr *addresses, + size_t size, + bool *with_leading_space, + bool (*predicate)(const struct in_addr *addr)); +int deserialize_in_addrs(struct in_addr **addresses, const char *string); +void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, + size_t size, + bool *with_leading_space); +int deserialize_in6_addrs(struct in6_addr **addresses, const char *string); + +/* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */ +struct sd_dhcp_route; +struct sd_dhcp_lease; + +void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size); +int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, const char *string); + +/* It is not necessary to add deserialize_dhcp_option(). Use unhexmem() instead. */ +int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size); + +int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file); +int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file); diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-client.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-client.c new file mode 100644 index 00000000..94e43da3 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-client.c @@ -0,0 +1,2294 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include <errno.h> +#include <net/ethernet.h> +#include <net/if_arp.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <linux/if_infiniband.h> + +#include "sd-dhcp-client.h" + +#include "alloc-util.h" +#include "dhcp-identifier.h" +#include "dhcp-internal.h" +#include "dhcp-lease-internal.h" +#include "dhcp-protocol.h" +#include "dns-domain.h" +#include "event-util.h" +#include "fd-util.h" +#include "hostname-util.h" +#include "io-util.h" +#include "memory-util.h" +#include "network-common.h" +#include "random-util.h" +#include "set.h" +#include "sort-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "utf8.h" +#include "web-util.h" + +#define MAX_CLIENT_ID_LEN (sizeof(uint32_t) + MAX_DUID_LEN) /* Arbitrary limit */ +#define MAX_MAC_ADDR_LEN CONST_MAX(INFINIBAND_ALEN, ETH_ALEN) + +#define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC) +#define RESTART_AFTER_NAK_MAX_USEC (30 * USEC_PER_MINUTE) + +#define TRANSIENT_FAILURE_ATTEMPTS 3 /* Arbitrary limit: how many attempts are considered enough to report + * transient failure. */ + +typedef struct sd_dhcp_client_id { + uint8_t type; + union { + struct { + /* 0: Generic (non-LL) (RFC 2132) */ + uint8_t data[MAX_CLIENT_ID_LEN]; + } _packed_ gen; + struct { + /* 1: Ethernet Link-Layer (RFC 2132) */ + uint8_t haddr[ETH_ALEN]; + } _packed_ eth; + struct { + /* 2 - 254: ARP/Link-Layer (RFC 2132) */ + uint8_t haddr[0]; + } _packed_ ll; + struct { + /* 255: Node-specific (RFC 4361) */ + be32_t iaid; + struct duid duid; + } _packed_ ns; + struct { + uint8_t data[MAX_CLIENT_ID_LEN]; + } _packed_ raw; + }; +} _packed_ sd_dhcp_client_id; + +struct sd_dhcp_client { + unsigned n_ref; + + DHCPState state; + sd_event *event; + int event_priority; + sd_event_source *timeout_resend; + int ifindex; + char *ifname; + int fd; + uint16_t port; + union sockaddr_union link; + sd_event_source *receive_message; + bool request_broadcast; + Set *req_opts; + bool anonymize; + be32_t last_addr; + uint8_t mac_addr[MAX_MAC_ADDR_LEN]; + size_t mac_addr_len; + uint8_t bcast_addr[MAX_MAC_ADDR_LEN]; + size_t bcast_addr_len; + uint16_t arp_type; + sd_dhcp_client_id client_id; + size_t client_id_len; + char *hostname; + char *vendor_class_identifier; + char *mudurl; + char **user_class; + uint32_t mtu; + uint32_t fallback_lease_lifetime; + uint32_t xid; + usec_t start_time; + usec_t t1_time; + usec_t t2_time; + usec_t expire_time; + uint64_t attempt; + uint64_t max_attempts; + OrderedHashmap *extra_options; + OrderedHashmap *vendor_options; + usec_t request_sent; + sd_event_source *timeout_t1; + sd_event_source *timeout_t2; + sd_event_source *timeout_expire; + sd_dhcp_client_callback_t callback; + void *userdata; + sd_dhcp_lease *lease; + usec_t start_delay; + int ip_service_type; +}; + +static const uint8_t default_req_opts[] = { + SD_DHCP_OPTION_SUBNET_MASK, + SD_DHCP_OPTION_ROUTER, + SD_DHCP_OPTION_HOST_NAME, + SD_DHCP_OPTION_DOMAIN_NAME, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, +}; + +/* RFC7844 section 3: + MAY contain the Parameter Request List option. + RFC7844 section 3.6: + The client intending to protect its privacy SHOULD only request a + minimal number of options in the PRL and SHOULD also randomly shuffle + the ordering of option codes in the PRL. If this random ordering + cannot be implemented, the client MAY order the option codes in the + PRL by option code number (lowest to highest). +*/ +/* NOTE: using PRL options that Windows 10 RFC7844 implementation uses */ +static const uint8_t default_req_opts_anonymize[] = { + SD_DHCP_OPTION_SUBNET_MASK, /* 1 */ + SD_DHCP_OPTION_ROUTER, /* 3 */ + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, /* 6 */ + SD_DHCP_OPTION_DOMAIN_NAME, /* 15 */ + SD_DHCP_OPTION_ROUTER_DISCOVER, /* 31 */ + SD_DHCP_OPTION_STATIC_ROUTE, /* 33 */ + SD_DHCP_OPTION_VENDOR_SPECIFIC, /* 43 */ + SD_DHCP_OPTION_NETBIOS_NAMESERVER, /* 44 */ + SD_DHCP_OPTION_NETBIOS_NODETYPE, /* 46 */ + SD_DHCP_OPTION_NETBIOS_SCOPE, /* 47 */ + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, /* 121 */ + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, /* 249 */ + SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, /* 252 */ +}; + +static int client_receive_message_raw( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata); +static int client_receive_message_udp( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata); +static void client_stop(sd_dhcp_client *client, int error); + +int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret) { + const sd_dhcp_client_id *client_id = data; + _cleanup_free_ char *t = NULL; + int r = 0; + + assert_return(data, -EINVAL); + assert_return(len >= 1, -EINVAL); + assert_return(ret, -EINVAL); + + len -= 1; + if (len > MAX_CLIENT_ID_LEN) + return -EINVAL; + + switch (client_id->type) { + case 0: + if (utf8_is_printable((char *) client_id->gen.data, len)) + r = asprintf(&t, "%.*s", (int) len, client_id->gen.data); + else + r = asprintf(&t, "DATA"); + break; + case 1: + if (len != sizeof_field(sd_dhcp_client_id, eth)) + return -EINVAL; + + r = asprintf(&t, "%x:%x:%x:%x:%x:%x", + client_id->eth.haddr[0], + client_id->eth.haddr[1], + client_id->eth.haddr[2], + client_id->eth.haddr[3], + client_id->eth.haddr[4], + client_id->eth.haddr[5]); + break; + case 2 ... 254: + r = asprintf(&t, "ARP/LL"); + break; + case 255: + if (len < 6) + return -EINVAL; + + uint32_t iaid = be32toh(client_id->ns.iaid); + uint16_t duid_type = be16toh(client_id->ns.duid.type); + if (dhcp_validate_duid_len(duid_type, len - 6, true) < 0) + return -EINVAL; + + r = asprintf(&t, "IAID:0x%x/DUID", iaid); + break; + } + + if (r < 0) + return -ENOMEM; + *ret = TAKE_PTR(t); + return 0; +} + +int sd_dhcp_client_set_callback( + sd_dhcp_client *client, + sd_dhcp_client_callback_t cb, + void *userdata) { + + assert_return(client, -EINVAL); + + client->callback = cb; + client->userdata = userdata; + + return 0; +} + +int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast) { + assert_return(client, -EINVAL); + + client->request_broadcast = !!broadcast; + + return 0; +} + +int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) { + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY); + + switch(option) { + + case SD_DHCP_OPTION_PAD: + case SD_DHCP_OPTION_OVERLOAD: + case SD_DHCP_OPTION_MESSAGE_TYPE: + case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST: + case SD_DHCP_OPTION_END: + return -EINVAL; + + default: + break; + } + + return set_ensure_put(&client->req_opts, NULL, UINT8_TO_PTR(option)); +} + +int sd_dhcp_client_set_request_address( + sd_dhcp_client *client, + const struct in_addr *last_addr) { + + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY); + + if (last_addr) + client->last_addr = last_addr->s_addr; + else + client->last_addr = INADDR_ANY; + + return 0; +} + +int sd_dhcp_client_set_ifindex(sd_dhcp_client *client, int ifindex) { + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY); + assert_return(ifindex > 0, -EINVAL); + + client->ifindex = ifindex; + return 0; +} + +int sd_dhcp_client_set_ifname(sd_dhcp_client *client, const char *ifname) { + assert_return(client, -EINVAL); + assert_return(ifname, -EINVAL); + + if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE)) + return -EINVAL; + + return free_and_strdup(&client->ifname, ifname); +} + +const char *sd_dhcp_client_get_ifname(sd_dhcp_client *client) { + if (!client) + return NULL; + + return get_ifname(client->ifindex, &client->ifname); +} + +int sd_dhcp_client_set_mac( + sd_dhcp_client *client, + const uint8_t *addr, + const uint8_t *bcast_addr, + size_t addr_len, + uint16_t arp_type) { + + DHCP_CLIENT_DONT_DESTROY(client); + bool need_restart = false; + int r; + + assert_return(client, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(addr_len > 0 && addr_len <= MAX_MAC_ADDR_LEN, -EINVAL); + assert_return(arp_type > 0, -EINVAL); + + if (arp_type == ARPHRD_ETHER) + assert_return(addr_len == ETH_ALEN, -EINVAL); + else if (arp_type == ARPHRD_INFINIBAND) + assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); + else + return -EINVAL; + + if (client->mac_addr_len == addr_len && + memcmp(&client->mac_addr, addr, addr_len) == 0 && + (client->bcast_addr_len > 0) == !!bcast_addr && + (!bcast_addr || memcmp(&client->bcast_addr, bcast_addr, addr_len) == 0)) + return 0; + + if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) { + log_dhcp_client(client, "Changing MAC address on running DHCP client, restarting"); + need_restart = true; + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + } + + memcpy(&client->mac_addr, addr, addr_len); + client->mac_addr_len = addr_len; + client->arp_type = arp_type; + client->bcast_addr_len = 0; + + if (bcast_addr) { + memcpy(&client->bcast_addr, bcast_addr, addr_len); + client->bcast_addr_len = addr_len; + } + + if (need_restart && client->state != DHCP_STATE_STOPPED) { + r = sd_dhcp_client_start(client); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to restart DHCPv4 client: %m"); + } + + return 0; +} + +int sd_dhcp_client_get_client_id( + sd_dhcp_client *client, + uint8_t *type, + const uint8_t **data, + size_t *data_len) { + + assert_return(client, -EINVAL); + assert_return(type, -EINVAL); + assert_return(data, -EINVAL); + assert_return(data_len, -EINVAL); + + if (client->client_id_len) { + *type = client->client_id.type; + *data = client->client_id.raw.data; + *data_len = client->client_id_len - sizeof(client->client_id.type); + } else { + *type = 0; + *data = NULL; + *data_len = 0; + } + + return 0; +} + +int sd_dhcp_client_set_client_id( + sd_dhcp_client *client, + uint8_t type, + const uint8_t *data, + size_t data_len) { + + DHCP_CLIENT_DONT_DESTROY(client); + bool need_restart = false; + int r; + + assert_return(client, -EINVAL); + assert_return(data, -EINVAL); + assert_return(data_len > 0 && data_len <= MAX_CLIENT_ID_LEN, -EINVAL); + G_STATIC_ASSERT_EXPR (_NM_MAX_CLIENT_ID_LEN == MAX_CLIENT_ID_LEN); + + if (client->client_id_len == data_len + sizeof(client->client_id.type) && + client->client_id.type == type && + memcmp(&client->client_id.raw.data, data, data_len) == 0) + return 0; + + /* For hardware types, log debug message about unexpected data length. + * + * Note that infiniband's INFINIBAND_ALEN is 20 bytes long, but only + * the last 8 bytes of the address are stable and suitable to put into + * the client-id. The caller is advised to account for that. */ + if ((type == ARPHRD_ETHER && data_len != ETH_ALEN) || + (type == ARPHRD_INFINIBAND && data_len != 8)) + log_dhcp_client(client, "Changing client ID to hardware type %u with " + "unexpected address length %zu", + type, data_len); + + if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) { + log_dhcp_client(client, "Changing client ID on running DHCP " + "client, restarting"); + need_restart = true; + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + } + + client->client_id.type = type; + memcpy(&client->client_id.raw.data, data, data_len); + client->client_id_len = data_len + sizeof (client->client_id.type); + + if (need_restart && client->state != DHCP_STATE_STOPPED) { + r = sd_dhcp_client_start(client); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to restart DHCPv4 client: %m"); + } + + return 0; +} + +#if 0 /* NM_IGNORED */ +/** + * Sets IAID and DUID. If duid is non-null, the DUID is set to duid_type + duid + * without further modification. Otherwise, if duid_type is supported, DUID + * is set based on that type. Otherwise, an error is returned. + */ +static int dhcp_client_set_iaid_duid_internal( + sd_dhcp_client *client, + bool iaid_append, + bool iaid_set, + uint32_t iaid, + uint16_t duid_type, + const void *duid, + size_t duid_len, + usec_t llt_time) { + + DHCP_CLIENT_DONT_DESTROY(client); + int r; + size_t len; + + assert_return(client, -EINVAL); + assert_return(duid_len == 0 || duid, -EINVAL); + + if (duid) { + r = dhcp_validate_duid_len(duid_type, duid_len, true); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to validate length of DUID: %m"); + } + + zero(client->client_id); + client->client_id.type = 255; + + if (iaid_append) { + if (iaid_set) + client->client_id.ns.iaid = htobe32(iaid); + else { + r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, + client->mac_addr_len, + true, + &client->client_id.ns.iaid); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to set IAID: %m"); + } + } + + if (duid) { + client->client_id.ns.duid.type = htobe16(duid_type); + memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len); + len = sizeof(client->client_id.ns.duid.type) + duid_len; + } else + switch (duid_type) { + case DUID_TYPE_LLT: + if (client->mac_addr_len == 0) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LLT, MAC address is not set."); + + r = dhcp_identifier_set_duid_llt(&client->client_id.ns.duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &len); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to set DUID-LLT: %m"); + break; + case DUID_TYPE_EN: + r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to set DUID-EN: %m"); + break; + case DUID_TYPE_LL: + if (client->mac_addr_len == 0) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LL, MAC address is not set."); + + r = dhcp_identifier_set_duid_ll(&client->client_id.ns.duid, client->mac_addr, client->mac_addr_len, client->arp_type, &len); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to set DUID-LL: %m"); + break; + case DUID_TYPE_UUID: + r = dhcp_identifier_set_duid_uuid(&client->client_id.ns.duid, &len); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to set DUID-UUID: %m"); + break; + default: + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Invalid DUID type"); + } + + client->client_id_len = sizeof(client->client_id.type) + len + + (iaid_append ? sizeof(client->client_id.ns.iaid) : 0); + + if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) { + log_dhcp_client(client, "Configured %sDUID, restarting.", iaid_append ? "IAID+" : ""); + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + r = sd_dhcp_client_start(client); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to restart DHCPv4 client: %m"); + } + + return 0; +} + +int sd_dhcp_client_set_iaid_duid( + sd_dhcp_client *client, + bool iaid_set, + uint32_t iaid, + uint16_t duid_type, + const void *duid, + size_t duid_len) { + return dhcp_client_set_iaid_duid_internal(client, true, iaid_set, iaid, duid_type, duid, duid_len, 0); +} + +int sd_dhcp_client_set_iaid_duid_llt( + sd_dhcp_client *client, + bool iaid_set, + uint32_t iaid, + usec_t llt_time) { + return dhcp_client_set_iaid_duid_internal(client, true, iaid_set, iaid, DUID_TYPE_LLT, NULL, 0, llt_time); +} + +int sd_dhcp_client_set_duid( + sd_dhcp_client *client, + uint16_t duid_type, + const void *duid, + size_t duid_len) { + return dhcp_client_set_iaid_duid_internal(client, false, false, 0, duid_type, duid, duid_len, 0); +} + +int sd_dhcp_client_set_duid_llt( + sd_dhcp_client *client, + usec_t llt_time) { + return dhcp_client_set_iaid_duid_internal(client, false, false, 0, DUID_TYPE_LLT, NULL, 0, llt_time); +} +#endif /* NM_IGNORED */ + +int sd_dhcp_client_set_hostname( + sd_dhcp_client *client, + const char *hostname) { + + assert_return(client, -EINVAL); + + /* Make sure hostnames qualify as DNS and as Linux hostnames */ + if (hostname && + !(hostname_is_valid(hostname, 0) && dns_name_is_valid(hostname) > 0)) + return -EINVAL; + + return free_and_strdup(&client->hostname, hostname); +} + +int sd_dhcp_client_set_vendor_class_identifier( + sd_dhcp_client *client, + const char *vci) { + + assert_return(client, -EINVAL); + + return free_and_strdup(&client->vendor_class_identifier, vci); +} + +int sd_dhcp_client_set_mud_url( + sd_dhcp_client *client, + const char *mudurl) { + + assert_return(client, -EINVAL); + assert_return(mudurl, -EINVAL); + assert_return(strlen(mudurl) <= 255, -EINVAL); + assert_return(http_url_is_valid(mudurl), -EINVAL); + + return free_and_strdup(&client->mudurl, mudurl); +} + +int sd_dhcp_client_set_user_class( + sd_dhcp_client *client, + char * const *user_class) { + + char * const *p; + char **s = NULL; + + assert_return(client, -EINVAL); + assert_return(!strv_isempty(user_class), -EINVAL); + + STRV_FOREACH(p, user_class) { + size_t n = strlen(*p); + + if (n > 255 || n == 0) + return -EINVAL; + } + + s = strv_copy(user_class); + if (!s) + return -ENOMEM; + + return strv_free_and_replace(client->user_class, s); +} + +int sd_dhcp_client_set_client_port( + sd_dhcp_client *client, + uint16_t port) { + + assert_return(client, -EINVAL); + + client->port = port; + + return 0; +} + +int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) { + assert_return(client, -EINVAL); + assert_return(mtu >= DHCP_DEFAULT_MIN_SIZE, -ERANGE); + + client->mtu = mtu; + + return 0; +} + +int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempts) { + assert_return(client, -EINVAL); + + client->max_attempts = max_attempts; + + return 0; +} + +int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v) { + int r; + + assert_return(client, -EINVAL); + assert_return(v, -EINVAL); + + r = ordered_hashmap_ensure_put(&client->extra_options, &dhcp_option_hash_ops, UINT_TO_PTR(v->option), v); + if (r < 0) + return r; + + sd_dhcp_option_ref(v); + return 0; +} + +int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v) { + int r; + + assert_return(client, -EINVAL); + assert_return(v, -EINVAL); + + r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp_option_hash_ops); + if (r < 0) + return -ENOMEM; + + r = ordered_hashmap_put(client->vendor_options, v, v); + if (r < 0) + return r; + + sd_dhcp_option_ref(v); + + return 1; +} + +int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) { + assert_return(client, -EINVAL); + + if (!IN_SET(client->state, DHCP_STATE_SELECTING, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING)) + return -EADDRNOTAVAIL; + + if (ret) + *ret = client->lease; + + return 0; +} + +int sd_dhcp_client_set_service_type(sd_dhcp_client *client, int type) { + assert_return(client, -EINVAL); + + client->ip_service_type = type; + + return 0; +} + +int sd_dhcp_client_set_fallback_lease_lifetime(sd_dhcp_client *client, uint32_t fallback_lease_lifetime) { + assert_return(client, -EINVAL); + assert_return(fallback_lease_lifetime > 0, -EINVAL); + + client->fallback_lease_lifetime = fallback_lease_lifetime; + + return 0; +} + +static int client_notify(sd_dhcp_client *client, int event) { + assert(client); + + if (client->callback) + return client->callback(client, event, client->userdata); + + return 0; +} + +static int client_initialize(sd_dhcp_client *client) { + assert_return(client, -EINVAL); + + client->receive_message = sd_event_source_unref(client->receive_message); + + client->fd = safe_close(client->fd); + + (void) event_source_disable(client->timeout_resend); + (void) event_source_disable(client->timeout_t1); + (void) event_source_disable(client->timeout_t2); + (void) event_source_disable(client->timeout_expire); + + client->attempt = 0; + + client->state = DHCP_STATE_INIT; + client->xid = 0; + + client->lease = sd_dhcp_lease_unref(client->lease); + + return 0; +} + +static void client_stop(sd_dhcp_client *client, int error) { + assert(client); + + if (error < 0) + log_dhcp_client_errno(client, error, "STOPPED: %m"); + else if (error == SD_DHCP_CLIENT_EVENT_STOP) + log_dhcp_client(client, "STOPPED"); + else + log_dhcp_client(client, "STOPPED: Unknown event"); + + client_notify(client, error); + + client_initialize(client); +} + +/* RFC2131 section 4.1: + * retransmission delays should include -1 to +1 sec of random 'fuzz'. */ +#define RFC2131_RANDOM_FUZZ \ + ((int64_t)(random_u64() % (2 * USEC_PER_SEC)) - (int64_t)USEC_PER_SEC) + +/* RFC2131 section 4.1: + * for retransmission delays, timeout should start at 4s then double + * each attempt with max of 64s, with -1 to +1 sec of random 'fuzz' added. + * This assumes the first call will be using attempt 1. */ +static usec_t client_compute_request_timeout(usec_t now, uint64_t attempt) { + usec_t timeout = (UINT64_C(1) << MIN(attempt + 1, UINT64_C(6))) * USEC_PER_SEC; + + return usec_sub_signed(usec_add(now, timeout), RFC2131_RANDOM_FUZZ); +} + +/* RFC2131 section 4.4.5: + * T1 defaults to (0.5 * duration_of_lease). + * T2 defaults to (0.875 * duration_of_lease). */ +#define T1_DEFAULT(lifetime) ((lifetime) / 2) +#define T2_DEFAULT(lifetime) (((lifetime) * 7) / 8) + +/* RFC2131 section 4.4.5: + * the client SHOULD wait one-half of the remaining time until T2 (in RENEWING state) + * and one-half of the remaining lease time (in REBINDING state), down to a minimum + * of 60 seconds. + * Note that while the default T1/T2 initial times do have random 'fuzz' applied, + * the RFC sec 4.4.5 does not mention adding any fuzz to retries. */ +static usec_t client_compute_reacquisition_timeout(usec_t now, usec_t expire) { + return now + MAX(usec_sub_unsigned(expire, now) / 2, 60 * USEC_PER_SEC); +} + +static int cmp_uint8(const uint8_t *a, const uint8_t *b) { + return CMP(*a, *b); +} + +static int client_message_init( + sd_dhcp_client *client, + DHCPPacket **ret, + uint8_t type, + size_t *_optlen, + size_t *_optoffset) { + + _cleanup_free_ DHCPPacket *packet = NULL; + size_t optlen, optoffset, size; + be16_t max_size; + usec_t time_now; + uint16_t secs; + int r; + + assert(client); + assert(client->start_time); + assert(ret); + assert(_optlen); + assert(_optoffset); + assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE)); + + optlen = DHCP_MIN_OPTIONS_SIZE; + size = sizeof(DHCPPacket) + optlen; + + packet = malloc0(size); + if (!packet) + return -ENOMEM; + + r = dhcp_message_init(&packet->dhcp, BOOTREQUEST, client->xid, type, + client->arp_type, optlen, &optoffset); + if (r < 0) + return r; + + /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers + refuse to issue an DHCP lease if 'secs' is set to zero */ + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; + assert(time_now >= client->start_time); + + /* seconds between sending first and last DISCOVER + * must always be strictly positive to deal with broken servers */ + secs = ((time_now - client->start_time) / USEC_PER_SEC) ? : 1; + packet->dhcp.secs = htobe16(secs); + + /* RFC2132 section 4.1 + A client that cannot receive unicast IP datagrams until its protocol + software has been configured with an IP address SHOULD set the + BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or + DHCPREQUEST messages that client sends. The BROADCAST bit will + provide a hint to the DHCP server and BOOTP relay agent to broadcast + any messages to the client on the client's subnet. + + Note: some interfaces needs this to be enabled, but some networks + needs this to be disabled as broadcasts are filteretd, so this + needs to be configurable */ + if (client->request_broadcast || client->arp_type != ARPHRD_ETHER) + packet->dhcp.flags = htobe16(0x8000); + + /* RFC2132 section 4.1.1: + The client MUST include its hardware address in the ’chaddr’ field, if + necessary for delivery of DHCP reply messages. Non-Ethernet + interfaces will leave 'chaddr' empty and use the client identifier + instead (eg, RFC 4390 section 2.1). + */ + if (client->arp_type == ARPHRD_ETHER) + memcpy(&packet->dhcp.chaddr, &client->mac_addr, ETH_ALEN); + + /* If no client identifier exists, construct an RFC 4361-compliant one */ + if (client->client_id_len == 0) { + size_t duid_len; + + client->client_id.type = 255; + + r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, + true, &client->client_id.ns.iaid); + if (r < 0) + return r; + + r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &duid_len); + if (r < 0) + return r; + + client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + duid_len; + } + + /* Some DHCP servers will refuse to issue an DHCP lease if the Client + Identifier option is not set */ + if (client->client_id_len) { + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_CLIENT_IDENTIFIER, + client->client_id_len, + &client->client_id); + if (r < 0) + return r; + } + + /* RFC2131 section 3.5: + in its initial DHCPDISCOVER or DHCPREQUEST message, a + client may provide the server with a list of specific + parameters the client is interested in. If the client + includes a list of parameters in a DHCPDISCOVER message, + it MUST include that list in any subsequent DHCPREQUEST + messages. + */ + + /* RFC7844 section 3: + MAY contain the Parameter Request List option. */ + /* NOTE: in case that there would be an option to do not send + * any PRL at all, the size should be checked before sending */ + if (!set_isempty(client->req_opts) && type != DHCP_RELEASE) { + _cleanup_free_ uint8_t *opts = NULL; + size_t n_opts, i = 0; + void *val; + + n_opts = set_size(client->req_opts); + opts = new(uint8_t, n_opts); + if (!opts) + return -ENOMEM; + + SET_FOREACH(val, client->req_opts) + opts[i++] = PTR_TO_UINT8(val); + assert(i == n_opts); + + /* For anonymizing the request, let's sort the options. */ + typesafe_qsort(opts, n_opts, cmp_uint8); + + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, + n_opts, opts); + if (r < 0) + return r; + } + + /* RFC2131 section 3.5: + The client SHOULD include the ’maximum DHCP message size’ option to + let the server know how large the server may make its DHCP messages. + + Note (from ConnMan): Some DHCP servers will send bigger DHCP packets + than the defined default size unless the Maximum Message Size option + is explicitly set + + RFC3442 "Requirements to Avoid Sizing Constraints": + Because a full routing table can be quite large, the standard 576 + octet maximum size for a DHCP message may be too short to contain + some legitimate Classless Static Route options. Because of this, + clients implementing the Classless Static Route option SHOULD send a + Maximum DHCP Message Size [4] option if the DHCP client's TCP/IP + stack is capable of receiving larger IP datagrams. In this case, the + client SHOULD set the value of this option to at least the MTU of the + interface that the client is configuring. The client MAY set the + value of this option higher, up to the size of the largest UDP packet + it is prepared to accept. (Note that the value specified in the + Maximum DHCP Message Size option is the total maximum packet size, + including IP and UDP headers.) + */ + /* RFC7844 section 3: + SHOULD NOT contain any other option. */ + if (!client->anonymize && type != DHCP_RELEASE) { + max_size = htobe16(size); + r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, + 2, &max_size); + if (r < 0) + return r; + } + + *_optlen = optlen; + *_optoffset = optoffset; + *ret = TAKE_PTR(packet); + + return 0; +} + +static int client_append_fqdn_option( + DHCPMessage *message, + size_t optlen, + size_t *optoffset, + const char *fqdn) { + + uint8_t buffer[3 + DHCP_MAX_FQDN_LENGTH]; + int r; + + buffer[0] = DHCP_FQDN_FLAG_S | /* Request server to perform A RR DNS updates */ + DHCP_FQDN_FLAG_E; /* Canonical wire format */ + buffer[1] = 0; /* RCODE1 (deprecated) */ + buffer[2] = 0; /* RCODE2 (deprecated) */ + + r = dns_name_to_wire_format(fqdn, buffer + 3, sizeof(buffer) - 3, false); + if (r > 0) + r = dhcp_option_append(message, optlen, optoffset, 0, + SD_DHCP_OPTION_FQDN, 3 + r, buffer); + + return r; +} + +static int dhcp_client_send_raw( + sd_dhcp_client *client, + DHCPPacket *packet, + size_t len) { + + dhcp_packet_append_ip_headers(packet, INADDR_ANY, client->port, + INADDR_BROADCAST, DHCP_PORT_SERVER, len, client->ip_service_type); + + return dhcp_network_send_raw_socket(client->fd, &client->link, + packet, len); +} + +static int client_append_common_discover_request_options(sd_dhcp_client *client, DHCPPacket *packet, size_t *optoffset, size_t optlen) { + sd_dhcp_option *j; + int r; + + assert(client); + + if (client->hostname) { + /* According to RFC 4702 "clients that send the Client FQDN option in + their messages MUST NOT also send the Host Name option". Just send + one of the two depending on the hostname type. + */ + if (dns_name_is_single_label(client->hostname)) { + /* it is unclear from RFC 2131 if client should send hostname in + DHCPDISCOVER but dhclient does and so we do as well + */ + r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, + SD_DHCP_OPTION_HOST_NAME, + strlen(client->hostname), client->hostname); + } else + r = client_append_fqdn_option(&packet->dhcp, optlen, optoffset, + client->hostname); + if (r < 0) + return r; + } + + if (client->vendor_class_identifier) { + r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, + strlen(client->vendor_class_identifier), + client->vendor_class_identifier); + if (r < 0) + return r; + } + + if (client->mudurl) { + r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, + SD_DHCP_OPTION_MUD_URL, + strlen(client->mudurl), + client->mudurl); + if (r < 0) + return r; + } + + if (client->user_class) { + r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, + SD_DHCP_OPTION_USER_CLASS, + strv_length(client->user_class), + client->user_class); + if (r < 0) + return r; + } + + ORDERED_HASHMAP_FOREACH(j, client->extra_options) { + r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, + j->option, j->length, j->data); + if (r < 0) + return r; + } + + if (!ordered_hashmap_isempty(client->vendor_options)) { + r = dhcp_option_append( + &packet->dhcp, optlen, optoffset, 0, + SD_DHCP_OPTION_VENDOR_SPECIFIC, + ordered_hashmap_size(client->vendor_options), client->vendor_options); + if (r < 0) + return r; + } + + + return 0; +} + +static int client_send_discover(sd_dhcp_client *client) { + _cleanup_free_ DHCPPacket *discover = NULL; + size_t optoffset, optlen; + int r; + + assert(client); + assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING)); + + r = client_message_init(client, &discover, DHCP_DISCOVER, + &optlen, &optoffset); + if (r < 0) + return r; + + /* the client may suggest values for the network address + and lease time in the DHCPDISCOVER message. The client may include + the ’requested IP address’ option to suggest that a particular IP + address be assigned, and may include the ’IP address lease time’ + option to suggest the lease time it would like. + */ + /* RFC7844 section 3: + SHOULD NOT contain any other option. */ + if (!client->anonymize && client->last_addr != INADDR_ANY) { + r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, + 4, &client->last_addr); + if (r < 0) + return r; + } + + r = client_append_common_discover_request_options(client, discover, &optoffset, optlen); + if (r < 0) + return r; + + r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + /* We currently ignore: + The client SHOULD wait a random time between one and ten seconds to + desynchronize the use of DHCP at startup. + */ + r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset); + if (r < 0) + return r; + + log_dhcp_client(client, "DISCOVER"); + + return 0; +} + +static int client_send_request(sd_dhcp_client *client) { + _cleanup_free_ DHCPPacket *request = NULL; + size_t optoffset, optlen; + int r; + + assert(client); + + r = client_message_init(client, &request, DHCP_REQUEST, &optlen, &optoffset); + if (r < 0) + return r; + + switch (client->state) { + /* See RFC2131 section 4.3.2 (note that there is a typo in the RFC, + SELECTING should be REQUESTING) + */ + + case DHCP_STATE_REQUESTING: + /* Client inserts the address of the selected server in ’server + identifier’, ’ciaddr’ MUST be zero, ’requested IP address’ MUST be + filled in with the yiaddr value from the chosen DHCPOFFER. + */ + + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + 4, &client->lease->server_address); + if (r < 0) + return r; + + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, + 4, &client->lease->address); + if (r < 0) + return r; + + break; + + case DHCP_STATE_INIT_REBOOT: + /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ + option MUST be filled in with client’s notion of its previously + assigned address. ’ciaddr’ MUST be zero. + */ + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, + 4, &client->last_addr); + if (r < 0) + return r; + break; + + case DHCP_STATE_RENEWING: + /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ + option MUST NOT be filled in, ’ciaddr’ MUST be filled in with + client’s IP address. + */ + + case DHCP_STATE_REBINDING: + /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ + option MUST NOT be filled in, ’ciaddr’ MUST be filled in with + client’s IP address. + + This message MUST be broadcast to the 0xffffffff IP broadcast address. + */ + request->dhcp.ciaddr = client->lease->address; + + break; + + case DHCP_STATE_INIT: + case DHCP_STATE_SELECTING: + case DHCP_STATE_REBOOTING: + case DHCP_STATE_BOUND: + case DHCP_STATE_STOPPED: + return -EINVAL; + } + + r = client_append_common_discover_request_options(client, request, &optoffset, optlen); + if (r < 0) + return r; + + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + if (client->state == DHCP_STATE_RENEWING) + r = dhcp_network_send_udp_socket(client->fd, + client->lease->server_address, + DHCP_PORT_SERVER, + &request->dhcp, + sizeof(DHCPMessage) + optoffset); + else + r = dhcp_client_send_raw(client, request, sizeof(DHCPPacket) + optoffset); + if (r < 0) + return r; + + switch (client->state) { + + case DHCP_STATE_REQUESTING: + log_dhcp_client(client, "REQUEST (requesting)"); + break; + + case DHCP_STATE_INIT_REBOOT: + log_dhcp_client(client, "REQUEST (init-reboot)"); + break; + + case DHCP_STATE_RENEWING: + log_dhcp_client(client, "REQUEST (renewing)"); + break; + + case DHCP_STATE_REBINDING: + log_dhcp_client(client, "REQUEST (rebinding)"); + break; + + default: + log_dhcp_client(client, "REQUEST (invalid)"); + break; + } + + return 0; +} + +static int client_start(sd_dhcp_client *client); + +static int client_timeout_resend( + sd_event_source *s, + uint64_t usec, + void *userdata) { + + sd_dhcp_client *client = userdata; + DHCP_CLIENT_DONT_DESTROY(client); + usec_t next_timeout; + uint64_t time_now; + int r; + + assert(s); + assert(client); + assert(client->event); + + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + goto error; + + switch (client->state) { + + case DHCP_STATE_RENEWING: + next_timeout = client_compute_reacquisition_timeout(time_now, client->t2_time); + break; + + case DHCP_STATE_REBINDING: + next_timeout = client_compute_reacquisition_timeout(time_now, client->expire_time); + break; + + case DHCP_STATE_REBOOTING: + /* start over as we did not receive a timely ack or nak */ + r = client_initialize(client); + if (r < 0) + goto error; + + r = client_start(client); + if (r < 0) + goto error; + + log_dhcp_client(client, "REBOOTED"); + return 0; + + case DHCP_STATE_INIT: + case DHCP_STATE_INIT_REBOOT: + case DHCP_STATE_SELECTING: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_BOUND: + if (client->attempt >= client->max_attempts) + goto error; + + client->attempt++; + next_timeout = client_compute_request_timeout(time_now, client->attempt); + break; + + case DHCP_STATE_STOPPED: + r = -EINVAL; + goto error; + + default: + assert_not_reached("Unhandled choice"); + } + + r = event_reset_time(client->event, &client->timeout_resend, + clock_boottime_or_monotonic(), + next_timeout, 10 * USEC_PER_MSEC, + client_timeout_resend, client, + client->event_priority, "dhcp4-resend-timer", true); + if (r < 0) + goto error; + + switch (client->state) { + case DHCP_STATE_INIT: + r = client_send_discover(client); + if (r >= 0) { + client->state = DHCP_STATE_SELECTING; + client->attempt = 0; + } else if (client->attempt >= client->max_attempts) + goto error; + + break; + + case DHCP_STATE_SELECTING: + r = client_send_discover(client); + if (r < 0 && client->attempt >= client->max_attempts) + goto error; + + break; + + case DHCP_STATE_INIT_REBOOT: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + r = client_send_request(client); + if (r < 0 && client->attempt >= client->max_attempts) + goto error; + + if (client->state == DHCP_STATE_INIT_REBOOT) + client->state = DHCP_STATE_REBOOTING; + + client->request_sent = time_now; + break; + + case DHCP_STATE_REBOOTING: + case DHCP_STATE_BOUND: + break; + + case DHCP_STATE_STOPPED: + r = -EINVAL; + goto error; + } + + if (client->attempt >= TRANSIENT_FAILURE_ATTEMPTS) + client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE); + + return 0; + +error: + client_stop(client, r); + + /* Errors were dealt with when stopping the client, don't spill + errors into the event loop handler */ + return 0; +} + +static int client_initialize_io_events( + sd_dhcp_client *client, + sd_event_io_handler_t io_callback) { + + int r; + + assert(client); + assert(client->event); + + r = sd_event_add_io(client->event, &client->receive_message, + client->fd, EPOLLIN, io_callback, + client); + if (r < 0) + goto error; + + r = sd_event_source_set_priority(client->receive_message, + client->event_priority); + if (r < 0) + goto error; + + r = sd_event_source_set_description(client->receive_message, "dhcp4-receive-message"); + if (r < 0) + goto error; + +error: + if (r < 0) + client_stop(client, r); + + return 0; +} + +static int client_initialize_time_events(sd_dhcp_client *client) { + uint64_t usec = 0; + int r; + + assert(client); + assert(client->event); + + if (client->start_delay > 0) { + assert_se(sd_event_now(client->event, clock_boottime_or_monotonic(), &usec) >= 0); + usec += client->start_delay; + } + + r = event_reset_time(client->event, &client->timeout_resend, + clock_boottime_or_monotonic(), + usec, 0, + client_timeout_resend, client, + client->event_priority, "dhcp4-resend-timer", true); + if (r < 0) + client_stop(client, r); + + return 0; + +} + +static int client_initialize_events(sd_dhcp_client *client, sd_event_io_handler_t io_callback) { + client_initialize_io_events(client, io_callback); + client_initialize_time_events(client); + + return 0; +} + +static int client_start_delayed(sd_dhcp_client *client) { + int r; + + assert_return(client, -EINVAL); + assert_return(client->event, -EINVAL); + assert_return(client->ifindex > 0, -EINVAL); + assert_return(client->fd < 0, -EBUSY); + assert_return(client->xid == 0, -EINVAL); + assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_INIT_REBOOT), -EBUSY); + + client->xid = random_u32(); + + r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid, + client->mac_addr, client->mac_addr_len, + client->bcast_addr, client->bcast_addr_len, + client->arp_type, client->port); + if (r < 0) { + client_stop(client, r); + return r; + } + client->fd = r; + + if (IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_INIT_REBOOT)) + client->start_time = now(clock_boottime_or_monotonic()); + + return client_initialize_events(client, client_receive_message_raw); +} + +static int client_start(sd_dhcp_client *client) { + client->start_delay = 0; + return client_start_delayed(client); +} + +static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp_client *client = userdata; + DHCP_CLIENT_DONT_DESTROY(client); + + log_dhcp_client(client, "EXPIRED"); + + client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); + + /* lease was lost, start over if not freed or stopped in callback */ + if (client->state != DHCP_STATE_STOPPED) { + client_initialize(client); + client_start(client); + } + + return 0; +} + +static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp_client *client = userdata; + DHCP_CLIENT_DONT_DESTROY(client); + int r; + + assert(client); + + client->receive_message = sd_event_source_unref(client->receive_message); + client->fd = safe_close(client->fd); + + client->state = DHCP_STATE_REBINDING; + client->attempt = 0; + + r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid, + client->mac_addr, client->mac_addr_len, + client->bcast_addr, client->bcast_addr_len, + client->arp_type, client->port); + if (r < 0) { + client_stop(client, r); + return 0; + } + client->fd = r; + + return client_initialize_events(client, client_receive_message_raw); +} + +static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp_client *client = userdata; + DHCP_CLIENT_DONT_DESTROY(client); + + if (client->lease) + client->state = DHCP_STATE_RENEWING; + else if (client->state != DHCP_STATE_INIT) + client->state = DHCP_STATE_INIT_REBOOT; + client->attempt = 0; + + return client_initialize_time_events(client); +} + +static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_t len) { + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + int r; + + r = dhcp_lease_new(&lease); + if (r < 0) + return r; + + if (client->client_id_len) { + r = dhcp_lease_set_client_id(lease, + (uint8_t *) &client->client_id, + client->client_id_len); + if (r < 0) + return r; + } + + r = dhcp_option_parse(offer, len, dhcp_lease_parse_options, lease, NULL); + if (r != DHCP_OFFER) { + log_dhcp_client(client, "received message was not an OFFER, ignoring"); + return -ENOMSG; + } + + lease->next_server = offer->siaddr; + lease->address = offer->yiaddr; + + if (lease->lifetime == 0 && client->fallback_lease_lifetime > 0) + lease->lifetime = client->fallback_lease_lifetime; + + if (lease->address == 0 || + lease->server_address == 0 || + lease->lifetime == 0) { + log_dhcp_client(client, "received lease lacks address, server address or lease lifetime, ignoring"); + return -ENOMSG; + } + + if (!lease->have_subnet_mask) { + r = dhcp_lease_set_default_subnet_mask(lease); + if (r < 0) { + log_dhcp_client(client, + "received lease lacks subnet mask, " + "and a fallback one cannot be generated, ignoring"); + return -ENOMSG; + } + } + + sd_dhcp_lease_unref(client->lease); + client->lease = TAKE_PTR(lease); + + if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0) + return -ENOMSG; + + log_dhcp_client(client, "OFFER"); + + return 0; +} + +static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, size_t len) { + int r; + + r = dhcp_option_parse(force, len, NULL, NULL, NULL); + if (r != DHCP_FORCERENEW) + return -ENOMSG; + +#if 0 /* NM_IGNORED */ + log_dhcp_client(client, "FORCERENEW"); + + return 0; +#else /* NM_IGNORED */ + /* NM: patch out the handling of FORCERENEW. We don't implement rfc3118 (Authentication + * for DHCP Messages) nor rfc6704 (Forcerenew Nonce Authentication) so accepting + * unauthenticated FORCERENEW requests is a security issue (CVE-2020-13529) + * See: https://github.com/systemd/systemd/issues/16774 */ + log_dhcp_client(client, "ignore FORCERENEW"); + return -ENOMSG; +#endif /* NM_IGNORED */ +} + +static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) { + if (a->address != b->address) + return false; + + if (a->subnet_mask != b->subnet_mask) + return false; + + if (a->router_size != b->router_size) + return false; + + for (size_t i = 0; i < a->router_size; i++) + if (a->router[i].s_addr != b->router[i].s_addr) + return false; + + return true; +} + +static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t len) { + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + _cleanup_free_ char *error_message = NULL; + int r; + + r = dhcp_lease_new(&lease); + if (r < 0) + return r; + + if (client->client_id_len) { + r = dhcp_lease_set_client_id(lease, + (uint8_t *) &client->client_id, + client->client_id_len); + if (r < 0) + return r; + } + + r = dhcp_option_parse(ack, len, dhcp_lease_parse_options, lease, &error_message); + if (r == DHCP_NAK) { + log_dhcp_client(client, "NAK: %s", strna(error_message)); + return -EADDRNOTAVAIL; + } + + if (r != DHCP_ACK) { + log_dhcp_client(client, "received message was not an ACK, ignoring"); + return -ENOMSG; + } + + lease->next_server = ack->siaddr; + + lease->address = ack->yiaddr; + + if (lease->address == INADDR_ANY || + lease->server_address == INADDR_ANY || + lease->lifetime == 0) { + log_dhcp_client(client, "received lease lacks address, server " + "address or lease lifetime, ignoring"); + return -ENOMSG; + } + + if (lease->subnet_mask == INADDR_ANY) { + r = dhcp_lease_set_default_subnet_mask(lease); + if (r < 0) { + log_dhcp_client(client, + "received lease lacks subnet mask, " + "and a fallback one cannot be generated, ignoring"); + return -ENOMSG; + } + } + + r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; + if (client->lease) { + if (lease_equal(client->lease, lease)) + r = SD_DHCP_CLIENT_EVENT_RENEW; + else + r = SD_DHCP_CLIENT_EVENT_IP_CHANGE; + + client->lease = sd_dhcp_lease_unref(client->lease); + } + + client->lease = TAKE_PTR(lease); + + log_dhcp_client(client, "ACK"); + + return r; +} + +static int client_set_lease_timeouts(sd_dhcp_client *client) { + usec_t time_now; + char time_string[FORMAT_TIMESPAN_MAX]; + int r; + + assert(client); + assert(client->event); + assert(client->lease); + assert(client->lease->lifetime); + + /* don't set timers for infinite leases */ + if (client->lease->lifetime == 0xffffffff) { + (void) event_source_disable(client->timeout_t1); + (void) event_source_disable(client->timeout_t2); + (void) event_source_disable(client->timeout_expire); + + return 0; + } + + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; + assert(client->request_sent <= time_now); + + /* verify that 0 < t2 < lifetime */ + if (client->lease->t2 == 0 || client->lease->t2 >= client->lease->lifetime) + client->lease->t2 = T2_DEFAULT(client->lease->lifetime); + /* verify that 0 < t1 < lifetime */ + if (client->lease->t1 == 0 || client->lease->t1 >= client->lease->t2) + client->lease->t1 = T1_DEFAULT(client->lease->lifetime); + /* now, if t1 >= t2, t1 *must* be T1_DEFAULT, since the previous check + * could not evalate to false if t1 >= t2; so setting t2 to T2_DEFAULT + * guarantees t1 < t2. */ + if (client->lease->t1 >= client->lease->t2) + client->lease->t2 = T2_DEFAULT(client->lease->lifetime); + + client->expire_time = client->request_sent + client->lease->lifetime * USEC_PER_SEC; + client->t1_time = client->request_sent + client->lease->t1 * USEC_PER_SEC; + client->t2_time = client->request_sent + client->lease->t2 * USEC_PER_SEC; + + /* RFC2131 section 4.4.5: + * Times T1 and T2 SHOULD be chosen with some random "fuzz". + * Since the RFC doesn't specify here the exact 'fuzz' to use, + * we use the range from section 4.1: -1 to +1 sec. */ + client->t1_time = usec_sub_signed(client->t1_time, RFC2131_RANDOM_FUZZ); + client->t2_time = usec_sub_signed(client->t2_time, RFC2131_RANDOM_FUZZ); + + /* after fuzzing, ensure t2 is still >= t1 */ + client->t2_time = MAX(client->t1_time, client->t2_time); + + /* arm lifetime timeout */ + r = event_reset_time(client->event, &client->timeout_expire, + clock_boottime_or_monotonic(), + client->expire_time, 10 * USEC_PER_MSEC, + client_timeout_expire, client, + client->event_priority, "dhcp4-lifetime", true); + if (r < 0) + return r; + + /* don't arm earlier timeouts if this has already expired */ + if (client->expire_time <= time_now) + return 0; + + log_dhcp_client(client, "lease expires in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->expire_time - time_now, USEC_PER_SEC)); + + /* arm T2 timeout */ + r = event_reset_time(client->event, &client->timeout_t2, + clock_boottime_or_monotonic(), + client->t2_time, 10 * USEC_PER_MSEC, + client_timeout_t2, client, + client->event_priority, "dhcp4-t2-timeout", true); + if (r < 0) + return r; + + /* don't arm earlier timeout if this has already expired */ + if (client->t2_time <= time_now) + return 0; + + log_dhcp_client(client, "T2 expires in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->t2_time - time_now, USEC_PER_SEC)); + + /* arm T1 timeout */ + r = event_reset_time(client->event, &client->timeout_t1, + clock_boottime_or_monotonic(), + client->t1_time, 10 * USEC_PER_MSEC, + client_timeout_t1, client, + client->event_priority, "dhcp4-t1-timer", true); + if (r < 0) + return r; + + if (client->t1_time > time_now) + log_dhcp_client(client, "T1 expires in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->t1_time - time_now, USEC_PER_SEC)); + + return 0; +} + +static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) { + DHCP_CLIENT_DONT_DESTROY(client); + char time_string[FORMAT_TIMESPAN_MAX]; + int r = 0, notify_event = 0; + + assert(client); + assert(client->event); + assert(message); + + switch (client->state) { + case DHCP_STATE_SELECTING: + + r = client_handle_offer(client, message, len); + if (r >= 0) { + + client->state = DHCP_STATE_REQUESTING; + client->attempt = 0; + + r = event_reset_time(client->event, &client->timeout_resend, + clock_boottime_or_monotonic(), + 0, 0, + client_timeout_resend, client, + client->event_priority, "dhcp4-resend-timer", true); + if (r < 0) + goto error; + } else if (r == -ENOMSG) + /* invalid message, let's ignore it */ + return 0; + + break; + + case DHCP_STATE_REBOOTING: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + + r = client_handle_ack(client, message, len); + if (r >= 0) { + client->start_delay = 0; + (void) event_source_disable(client->timeout_resend); + client->receive_message = + sd_event_source_unref(client->receive_message); + client->fd = safe_close(client->fd); + + if (IN_SET(client->state, DHCP_STATE_REQUESTING, + DHCP_STATE_REBOOTING)) + notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; + else if (r != SD_DHCP_CLIENT_EVENT_IP_ACQUIRE) + notify_event = r; + + client->state = DHCP_STATE_BOUND; + client->attempt = 0; + + client->last_addr = client->lease->address; + + r = client_set_lease_timeouts(client); + if (r < 0) { + log_dhcp_client(client, "could not set lease timeouts"); + goto error; + } + + r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type); + if (r < 0) { + log_dhcp_client(client, "could not bind UDP socket"); + goto error; + } + + client->fd = r; + + client_initialize_io_events(client, client_receive_message_udp); + + if (notify_event) { + client_notify(client, notify_event); + if (client->state == DHCP_STATE_STOPPED) + return 0; + } + + } else if (r == -EADDRNOTAVAIL) { + /* got a NAK, let's restart the client */ + client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); + + r = client_initialize(client); + if (r < 0) + goto error; + + r = client_start_delayed(client); + if (r < 0) + goto error; + + log_dhcp_client(client, "REBOOT in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX, + client->start_delay, USEC_PER_SEC)); + + client->start_delay = CLAMP(client->start_delay * 2, + RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC); + + return 0; + } else if (r == -ENOMSG) + /* invalid message, let's ignore it */ + return 0; + + break; + + case DHCP_STATE_BOUND: + r = client_handle_forcerenew(client, message, len); + if (r >= 0) { + r = client_timeout_t1(NULL, 0, client); + if (r < 0) + goto error; + } else if (r == -ENOMSG) + /* invalid message, let's ignore it */ + return 0; + + break; + + case DHCP_STATE_INIT: + case DHCP_STATE_INIT_REBOOT: + + break; + + case DHCP_STATE_STOPPED: + r = -EINVAL; + goto error; + } + +error: + if (r < 0) + client_stop(client, r); + + return r; +} + +static int client_receive_message_udp( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata) { + + sd_dhcp_client *client = userdata; + _cleanup_free_ DHCPMessage *message = NULL; + const uint8_t *expected_chaddr = NULL; + uint8_t expected_hlen = 0; + ssize_t len, buflen; + + assert(s); + assert(client); + + buflen = next_datagram_size_fd(fd); + if (buflen == -ENETDOWN) + /* the link is down. Don't return an error or the I/O event + source will be disconnected and we won't be able to receive + packets again when the link comes back. */ + return 0; + if (buflen < 0) + return buflen; + + message = malloc0(buflen); + if (!message) + return -ENOMEM; + + len = recv(fd, message, buflen, 0); + if (len < 0) { + /* see comment above for why we shouldn't error out on ENETDOWN. */ + if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN)) + return 0; + + return log_dhcp_client_errno(client, errno, + "Could not receive message from UDP socket: %m"); + } + if ((size_t) len < sizeof(DHCPMessage)) { + log_dhcp_client(client, "Too small to be a DHCP message: ignoring"); + return 0; + } + + if (be32toh(message->magic) != DHCP_MAGIC_COOKIE) { + log_dhcp_client(client, "Not a DHCP message: ignoring"); + return 0; + } + + if (message->op != BOOTREPLY) { + log_dhcp_client(client, "Not a BOOTREPLY message: ignoring"); + return 0; + } + + if (message->htype != client->arp_type) { + log_dhcp_client(client, "Packet type does not match client type"); + return 0; + } + + if (client->arp_type == ARPHRD_ETHER) { + expected_hlen = ETH_ALEN; + expected_chaddr = &client->mac_addr[0]; + } + + if (message->hlen != expected_hlen) { + log_dhcp_client(client, "Unexpected packet hlen %d", message->hlen); + return 0; + } + + if (expected_hlen > 0 && memcmp(&message->chaddr[0], expected_chaddr, expected_hlen)) { + log_dhcp_client(client, "Received chaddr does not match expected: ignoring"); + return 0; + } + + if (client->state != DHCP_STATE_BOUND && + be32toh(message->xid) != client->xid) { + /* in BOUND state, we may receive FORCERENEW with xid set by server, + so ignore the xid in this case */ + log_dhcp_client(client, "Received xid (%u) does not match expected (%u): ignoring", + be32toh(message->xid), client->xid); + return 0; + } + + return client_handle_message(client, message, len); +} + +static int client_receive_message_raw( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata) { + + sd_dhcp_client *client = userdata; + _cleanup_free_ DHCPPacket *packet = NULL; + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct tpacket_auxdata))) control; + struct iovec iov = {}; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + bool checksum = true; + ssize_t buflen, len; + int r; + + assert(s); + assert(client); + + buflen = next_datagram_size_fd(fd); + if (buflen == -ENETDOWN) + return 0; + if (buflen < 0) + return buflen; + + packet = malloc0(buflen); + if (!packet) + return -ENOMEM; + + iov = IOVEC_MAKE(packet, buflen); + + len = recvmsg_safe(fd, &msg, 0); + if (IN_SET(len, -EAGAIN, -EINTR, -ENETDOWN)) + return 0; + if (len < 0) + return log_dhcp_client_errno(client, len, + "Could not receive message from raw socket: %m"); + + if ((size_t) len < sizeof(DHCPPacket)) + return 0; + + cmsg = cmsg_find(&msg, SOL_PACKET, PACKET_AUXDATA, CMSG_LEN(sizeof(struct tpacket_auxdata))); + if (cmsg) { + struct tpacket_auxdata *aux = (struct tpacket_auxdata*) CMSG_DATA(cmsg); + checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY); + } + + r = dhcp_packet_verify_headers(packet, len, checksum, client->port); + if (r < 0) + return 0; + + len -= DHCP_IP_UDP_SIZE; + + return client_handle_message(client, &packet->dhcp, len); +} + +int sd_dhcp_client_send_renew(sd_dhcp_client *client) { + assert_return(client, -EINVAL); + assert_return(client->fd >= 0, -EINVAL); + + if (!client->lease) + return 0; + + client->start_delay = 0; + client->attempt = 1; + client->state = DHCP_STATE_RENEWING; + + return client_initialize_time_events(client); +} + +int sd_dhcp_client_is_running(sd_dhcp_client *client) { + if (!client) + return 0; + + return !IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED); +} + +int sd_dhcp_client_start(sd_dhcp_client *client) { + int r; + + assert_return(client, -EINVAL); + + r = client_initialize(client); + if (r < 0) + return r; + + /* RFC7844 section 3.3: + SHOULD perform a complete four-way handshake, starting with a + DHCPDISCOVER, to obtain a new address lease. If the client can + ascertain that this is exactly the same network to which it was + previously connected, and if the link-layer address did not change, + the client MAY issue a DHCPREQUEST to try to reclaim the current + address. */ + if (client->last_addr && !client->anonymize) + client->state = DHCP_STATE_INIT_REBOOT; + + r = client_start(client); + if (r >= 0) + log_dhcp_client(client, "STARTED on ifindex %i", client->ifindex); + + return r; +} + +int sd_dhcp_client_send_release(sd_dhcp_client *client) { + assert_return(client, -EINVAL); + assert_return(client->state != DHCP_STATE_STOPPED, -ESTALE); + assert_return(client->lease, -EUNATCH); + + _cleanup_free_ DHCPPacket *release = NULL; + size_t optoffset, optlen; + int r; + + r = client_message_init(client, &release, DHCP_RELEASE, &optlen, &optoffset); + if (r < 0) + return r; + + /* Fill up release IP and MAC */ + release->dhcp.ciaddr = client->lease->address; + memcpy(&release->dhcp.chaddr, &client->mac_addr, client->mac_addr_len); + + r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + r = dhcp_network_send_udp_socket(client->fd, + client->lease->server_address, + DHCP_PORT_SERVER, + &release->dhcp, + sizeof(DHCPMessage) + optoffset); + if (r < 0) + return r; + + log_dhcp_client(client, "RELEASE"); + + return 0; +} + +int sd_dhcp_client_send_decline(sd_dhcp_client *client) { + assert_return(client, -EINVAL); + assert_return(client->state != DHCP_STATE_STOPPED, -ESTALE); + assert_return(client->lease, -EUNATCH); + + _cleanup_free_ DHCPPacket *release = NULL; + size_t optoffset, optlen; + int r; + + r = client_message_init(client, &release, DHCP_DECLINE, &optlen, &optoffset); + if (r < 0) + return r; + + release->dhcp.ciaddr = client->lease->address; + memcpy(&release->dhcp.chaddr, &client->mac_addr, client->mac_addr_len); + + r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + r = dhcp_network_send_udp_socket(client->fd, + client->lease->server_address, + DHCP_PORT_SERVER, + &release->dhcp, + sizeof(DHCPMessage) + optoffset); + if (r < 0) + return r; + + log_dhcp_client(client, "DECLINE"); + + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + + if (client->state != DHCP_STATE_STOPPED) { + r = sd_dhcp_client_start(client); + if (r < 0) + return r; + } + + return 0; +} + +int sd_dhcp_client_stop(sd_dhcp_client *client) { + if (!client) + return 0; + + DHCP_CLIENT_DONT_DESTROY(client); + + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + client->state = DHCP_STATE_STOPPED; + + return 0; +} + +int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) { + int r; + + assert_return(client, -EINVAL); + assert_return(!client->event, -EBUSY); + + if (event) + client->event = sd_event_ref(event); + else { + r = sd_event_default(&client->event); + if (r < 0) + return 0; + } + + client->event_priority = priority; + + return 0; +} + +int sd_dhcp_client_detach_event(sd_dhcp_client *client) { + assert_return(client, -EINVAL); + + client->event = sd_event_unref(client->event); + + return 0; +} + +sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client) { + assert_return(client, NULL); + + return client->event; +} + +static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) { + if (!client) + return NULL; + + log_dhcp_client(client, "FREE"); + + client->timeout_resend = sd_event_source_unref(client->timeout_resend); + client->timeout_t1 = sd_event_source_unref(client->timeout_t1); + client->timeout_t2 = sd_event_source_unref(client->timeout_t2); + client->timeout_expire = sd_event_source_unref(client->timeout_expire); + + client_initialize(client); + + sd_dhcp_client_detach_event(client); + + sd_dhcp_lease_unref(client->lease); + + set_free(client->req_opts); + free(client->hostname); + free(client->vendor_class_identifier); + free(client->mudurl); + client->user_class = strv_free(client->user_class); + ordered_hashmap_free(client->extra_options); + ordered_hashmap_free(client->vendor_options); + free(client->ifname); + return mfree(client); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_client, sd_dhcp_client, dhcp_client_free); + +int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) { + const uint8_t *opts; + size_t n_opts; + int r; + + assert_return(ret, -EINVAL); + + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = new(sd_dhcp_client, 1); + if (!client) + return -ENOMEM; + + *client = (sd_dhcp_client) { + .n_ref = 1, + .state = DHCP_STATE_INIT, + .ifindex = -1, + .fd = -1, + .mtu = DHCP_DEFAULT_MIN_SIZE, + .port = DHCP_PORT_CLIENT, + .anonymize = !!anonymize, + .max_attempts = UINT64_MAX, + .ip_service_type = -1, + }; + /* NOTE: this could be moved to a function. */ + if (anonymize) { + n_opts = ELEMENTSOF(default_req_opts_anonymize); + opts = default_req_opts_anonymize; + } else { + n_opts = ELEMENTSOF(default_req_opts); + opts = default_req_opts; + } + + for (size_t i = 0; i < n_opts; i++) { + r = sd_dhcp_client_set_request_option(client, opts[i]); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(client); + + return 0; +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-lease.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-lease.c new file mode 100644 index 00000000..4cd9295c --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-lease.c @@ -0,0 +1,1388 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include <arpa/inet.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "sd-dhcp-lease.h" + +#include "alloc-util.h" +#include "dhcp-lease-internal.h" +#include "dhcp-protocol.h" +#include "dns-domain.h" +#include "env-file.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hexdecoct.h" +#include "hostname-util.h" +#include "in-addr-util.h" +#include "network-internal.h" +#include "parse-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "tmpfile-util.h" +#include "unaligned.h" + +int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (lease->address == 0) + return -ENODATA; + + addr->s_addr = lease->address; + return 0; +} + +int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (!lease->have_broadcast) + return -ENODATA; + + addr->s_addr = lease->broadcast; + return 0; +} + +int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime) { + assert_return(lease, -EINVAL); + assert_return(lifetime, -EINVAL); + + if (lease->lifetime <= 0) + return -ENODATA; + + *lifetime = lease->lifetime; + return 0; +} + +int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1) { + assert_return(lease, -EINVAL); + assert_return(t1, -EINVAL); + + if (lease->t1 <= 0) + return -ENODATA; + + *t1 = lease->t1; + return 0; +} + +int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2) { + assert_return(lease, -EINVAL); + assert_return(t2, -EINVAL); + + if (lease->t2 <= 0) + return -ENODATA; + + *t2 = lease->t2; + return 0; +} + +int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu) { + assert_return(lease, -EINVAL); + assert_return(mtu, -EINVAL); + + if (lease->mtu <= 0) + return -ENODATA; + + *mtu = lease->mtu; + return 0; +} + +int sd_dhcp_lease_get_servers( + sd_dhcp_lease *lease, + sd_dhcp_lease_server_type_t what, + const struct in_addr **addr) { + + assert_return(lease, -EINVAL); + assert_return(what >= 0, -EINVAL); + assert_return(what < _SD_DHCP_LEASE_SERVER_TYPE_MAX, -EINVAL); + assert_return(addr, -EINVAL); + + if (lease->servers[what].size <= 0) + return -ENODATA; + + *addr = lease->servers[what].addr; + return (int) lease->servers[what].size; +} + +int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_DNS, addr); +} +int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_NTP, addr); +} +int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SIP, addr); +} +int sd_dhcp_lease_get_pop3(sd_dhcp_lease *lease, const struct in_addr **addr) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_POP3, addr); +} +int sd_dhcp_lease_get_smtp(sd_dhcp_lease *lease, const struct in_addr **addr) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SMTP, addr); +} +int sd_dhcp_lease_get_lpr(sd_dhcp_lease *lease, const struct in_addr **addr) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_LPR, addr); +} + +int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname) { + assert_return(lease, -EINVAL); + assert_return(domainname, -EINVAL); + + if (!lease->domainname) + return -ENODATA; + + *domainname = lease->domainname; + return 0; +} + +int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname) { + assert_return(lease, -EINVAL); + assert_return(hostname, -EINVAL); + + if (!lease->hostname) + return -ENODATA; + + *hostname = lease->hostname; + return 0; +} + +int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path) { + assert_return(lease, -EINVAL); + assert_return(root_path, -EINVAL); + + if (!lease->root_path) + return -ENODATA; + + *root_path = lease->root_path; + return 0; +} + +int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (lease->router_size <= 0) + return -ENODATA; + + *addr = lease->router; + return (int) lease->router_size; +} + +int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (!lease->have_subnet_mask) + return -ENODATA; + + addr->s_addr = lease->subnet_mask; + return 0; +} + +int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (lease->server_address == 0) + return -ENODATA; + + addr->s_addr = lease->server_address; + return 0; +} + +int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (lease->next_server == 0) + return -ENODATA; + + addr->s_addr = lease->next_server; + return 0; +} + +/* + * The returned routes array must be freed by the caller. + * Route objects have the same lifetime of the lease and must not be freed. + */ +int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes) { + sd_dhcp_route **ret; + unsigned i; + + assert_return(lease, -EINVAL); + assert_return(routes, -EINVAL); + + if (lease->static_route_size <= 0) + return -ENODATA; + + ret = new(sd_dhcp_route *, lease->static_route_size); + if (!ret) + return -ENOMEM; + + for (i = 0; i < lease->static_route_size; i++) + ret[i] = &lease->static_route[i]; + + *routes = ret; + return (int) lease->static_route_size; +} + +int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains) { + size_t r; + + assert_return(lease, -EINVAL); + assert_return(domains, -EINVAL); + + r = strv_length(lease->search_domains); + if (r > 0) { + *domains = lease->search_domains; + return (int) r; + } + + return -ENODATA; +} + +int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len) { + assert_return(lease, -EINVAL); + assert_return(data, -EINVAL); + assert_return(data_len, -EINVAL); + + if (lease->vendor_specific_len <= 0) + return -ENODATA; + + *data = lease->vendor_specific; + *data_len = lease->vendor_specific_len; + return 0; +} + +static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { + assert(lease); + + while (lease->private_options) { + struct sd_dhcp_raw_option *option = lease->private_options; + + LIST_REMOVE(options, lease->private_options, option); + + free(option->data); + free(option); + } + + free(lease->root_path); + free(lease->router); + free(lease->timezone); + free(lease->hostname); + free(lease->domainname); + + for (sd_dhcp_lease_server_type_t i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++) + free(lease->servers[i].addr); + + free(lease->static_route); + free(lease->client_id); + free(lease->vendor_specific); + strv_free(lease->search_domains); + return mfree(lease); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_lease, sd_dhcp_lease, dhcp_lease_free); + +static int lease_parse_u32(const uint8_t *option, size_t len, uint32_t *ret, uint32_t min) { + assert(option); + assert(ret); + + if (len != 4) + return -EINVAL; + + *ret = unaligned_read_be32((be32_t*) option); + if (*ret < min) + *ret = min; + + return 0; +} + +static int lease_parse_u16(const uint8_t *option, size_t len, uint16_t *ret, uint16_t min) { + assert(option); + assert(ret); + + if (len != 2) + return -EINVAL; + + *ret = unaligned_read_be16((be16_t*) option); + if (*ret < min) + *ret = min; + + return 0; +} + +static int lease_parse_be32(const uint8_t *option, size_t len, be32_t *ret) { + assert(option); + assert(ret); + + if (len != 4) + return -EINVAL; + + memcpy(ret, option, 4); + return 0; +} + +static int lease_parse_string(const uint8_t *option, size_t len, char **ret) { + assert(option); + assert(ret); + + if (len <= 0) + *ret = mfree(*ret); + else { + char *string; + + /* + * One trailing NUL byte is OK, we don't mind. See: + * https://github.com/systemd/systemd/issues/1337 + */ + if (memchr(option, 0, len - 1)) + return -EINVAL; + + string = memdup_suffix0((const char *) option, len); + if (!string) + return -ENOMEM; + + free_and_replace(*ret, string); + } + + return 0; +} + +static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) { + _cleanup_free_ char *name = NULL, *normalized = NULL; + int r; + + assert(option); + assert(ret); + + r = lease_parse_string(option, len, &name); + if (r < 0) + return r; + if (!name) { + *ret = mfree(*ret); + return 0; + } + + r = dns_name_normalize(name, 0, &normalized); + if (r < 0) + return r; + + if (is_localhost(normalized)) + return -EINVAL; + + if (dns_name_is_root(normalized)) + return -EINVAL; + + free_and_replace(*ret, normalized); + + return 0; +} + +static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) { + assert(option || len == 0); + assert(ret); + assert(n_ret); + + if (len <= 0) { + *ret = mfree(*ret); + *n_ret = 0; + } else { + size_t n_addresses; + struct in_addr *addresses; + + if (len % 4 != 0) + return -EINVAL; + + n_addresses = len / 4; + + addresses = newdup(struct in_addr, option, n_addresses); + if (!addresses) + return -ENOMEM; + + free(*ret); + *ret = addresses; + *n_ret = n_addresses; + } + + return 0; +} + +static int lease_parse_sip_server(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) { + assert(option || len == 0); + assert(ret); + assert(n_ret); + + if (len <= 0) + return -EINVAL; + + /* The SIP record is like the other, regular server records, but prefixed with a single "encoding" + * byte that is either 0 or 1. We only support it to be 1 for now. Let's drop it and parse it like + * the other fields */ + + if (option[0] != 1) { /* We only support IP address encoding for now */ + *ret = mfree(*ret); + *n_ret = 0; + return 0; + } + + return lease_parse_in_addrs(option + 1, len - 1, ret, n_ret); +} + +static int lease_parse_routes( + const uint8_t *option, size_t len, + struct sd_dhcp_route **routes, size_t *routes_size) { + + struct in_addr addr; + + assert(option || len <= 0); + assert(routes); + assert(routes_size); + + if (len <= 0) + return 0; + + if (len % 8 != 0) + return -EINVAL; + + if (!GREEDY_REALLOC(*routes, *routes_size + (len / 8))) + return -ENOMEM; + + while (len >= 8) { + struct sd_dhcp_route *route = *routes + *routes_size; + int r; + + route->option = SD_DHCP_OPTION_STATIC_ROUTE; + r = in4_addr_default_prefixlen((struct in_addr*) option, &route->dst_prefixlen); + if (r < 0) { + log_debug("Failed to determine destination prefix length from class based IP, ignoring"); + continue; + } + + assert_se(lease_parse_be32(option, 4, &addr.s_addr) >= 0); + route->dst_addr = inet_makeaddr(inet_netof(addr), 0); + option += 4; + + assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0); + option += 4; + + len -= 8; + (*routes_size)++; + } + + return 0; +} + +/* parses RFC3442 Classless Static Route Option */ +static int lease_parse_classless_routes( + const uint8_t *option, size_t len, + struct sd_dhcp_route **routes, size_t *routes_size) { + + assert(option || len <= 0); + assert(routes); + assert(routes_size); + + if (len <= 0) + return 0; + + /* option format: (subnet-mask-width significant-subnet-octets gateway-ip)* */ + + while (len > 0) { + uint8_t dst_octets; + struct sd_dhcp_route *route; + + if (!GREEDY_REALLOC(*routes, *routes_size + 1)) + return -ENOMEM; + + route = *routes + *routes_size; + route->option = SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE; + + dst_octets = (*option == 0 ? 0 : ((*option - 1) / 8) + 1); + route->dst_prefixlen = *option; + option++; + len--; + + /* can't have more than 4 octets in IPv4 */ + if (dst_octets > 4 || len < dst_octets) + return -EINVAL; + + route->dst_addr.s_addr = 0; + memcpy(&route->dst_addr.s_addr, option, dst_octets); + option += dst_octets; + len -= dst_octets; + + if (len < 4) + return -EINVAL; + + assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0); + option += 4; + len -= 4; + + (*routes_size)++; + } + + return 0; +} + +int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata) { + sd_dhcp_lease *lease = userdata; + int r; + + assert(lease); + + switch(code) { + + case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: + r = lease_parse_u32(option, len, &lease->lifetime, 1); + if (r < 0) + log_debug_errno(r, "Failed to parse lease time, ignoring: %m"); + + break; + + case SD_DHCP_OPTION_SERVER_IDENTIFIER: + r = lease_parse_be32(option, len, &lease->server_address); + if (r < 0) + log_debug_errno(r, "Failed to parse server identifier, ignoring: %m"); + + break; + + case SD_DHCP_OPTION_SUBNET_MASK: + r = lease_parse_be32(option, len, &lease->subnet_mask); + if (r < 0) + log_debug_errno(r, "Failed to parse subnet mask, ignoring: %m"); + else + lease->have_subnet_mask = true; + break; + + case SD_DHCP_OPTION_BROADCAST: + r = lease_parse_be32(option, len, &lease->broadcast); + if (r < 0) + log_debug_errno(r, "Failed to parse broadcast address, ignoring: %m"); + else + lease->have_broadcast = true; + break; + + case SD_DHCP_OPTION_ROUTER: + r = lease_parse_in_addrs(option, len, &lease->router, &lease->router_size); + if (r < 0) + log_debug_errno(r, "Failed to parse router addresses, ignoring: %m"); + break; + + case SD_DHCP_OPTION_DOMAIN_NAME_SERVER: + r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_DNS].addr, &lease->servers[SD_DHCP_LEASE_DNS].size); + if (r < 0) + log_debug_errno(r, "Failed to parse DNS server, ignoring: %m"); + break; + + case SD_DHCP_OPTION_NTP_SERVER: + r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_NTP].addr, &lease->servers[SD_DHCP_LEASE_NTP].size); + if (r < 0) + log_debug_errno(r, "Failed to parse NTP server, ignoring: %m"); + break; + + case SD_DHCP_OPTION_SIP_SERVER: + r = lease_parse_sip_server(option, len, &lease->servers[SD_DHCP_LEASE_SIP].addr, &lease->servers[SD_DHCP_LEASE_SIP].size); + if (r < 0) + log_debug_errno(r, "Failed to parse SIP server, ignoring: %m"); + break; + + case SD_DHCP_OPTION_POP3_SERVER: + r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_POP3].addr, &lease->servers[SD_DHCP_LEASE_POP3].size); + if (r < 0) + log_debug_errno(r, "Failed to parse POP3 server, ignoring: %m"); + break; + + case SD_DHCP_OPTION_SMTP_SERVER: + r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_SMTP].addr, &lease->servers[SD_DHCP_LEASE_SMTP].size); + if (r < 0) + log_debug_errno(r, "Failed to parse SMTP server, ignoring: %m"); + break; + + case SD_DHCP_OPTION_LPR_SERVER: + r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_LPR].addr, &lease->servers[SD_DHCP_LEASE_LPR].size); + if (r < 0) + log_debug_errno(r, "Failed to parse LPR server, ignoring: %m"); + break; + + case SD_DHCP_OPTION_STATIC_ROUTE: + r = lease_parse_routes(option, len, &lease->static_route, &lease->static_route_size); + if (r < 0) + log_debug_errno(r, "Failed to parse static routes, ignoring: %m"); + break; + + case SD_DHCP_OPTION_INTERFACE_MTU: + r = lease_parse_u16(option, len, &lease->mtu, 68); + if (r < 0) + log_debug_errno(r, "Failed to parse MTU, ignoring: %m"); + if (lease->mtu < DHCP_DEFAULT_MIN_SIZE) { + log_debug("MTU value of %" PRIu16 " too small. Using default MTU value of %d instead.", lease->mtu, DHCP_DEFAULT_MIN_SIZE); + lease->mtu = DHCP_DEFAULT_MIN_SIZE; + } + + break; + + case SD_DHCP_OPTION_DOMAIN_NAME: + r = lease_parse_domain(option, len, &lease->domainname); + if (r < 0) { + log_debug_errno(r, "Failed to parse domain name, ignoring: %m"); + return 0; + } + + break; + + case SD_DHCP_OPTION_DOMAIN_SEARCH_LIST: + r = dhcp_lease_parse_search_domains(option, len, &lease->search_domains); + if (r < 0) + log_debug_errno(r, "Failed to parse Domain Search List, ignoring: %m"); + break; + + case SD_DHCP_OPTION_HOST_NAME: + r = lease_parse_domain(option, len, &lease->hostname); + if (r < 0) { + log_debug_errno(r, "Failed to parse hostname, ignoring: %m"); + return 0; + } + + break; + + case SD_DHCP_OPTION_ROOT_PATH: + r = lease_parse_string(option, len, &lease->root_path); + if (r < 0) + log_debug_errno(r, "Failed to parse root path, ignoring: %m"); + break; + + case SD_DHCP_OPTION_RENEWAL_T1_TIME: + r = lease_parse_u32(option, len, &lease->t1, 1); + if (r < 0) + log_debug_errno(r, "Failed to parse T1 time, ignoring: %m"); + break; + + case SD_DHCP_OPTION_REBINDING_T2_TIME: + r = lease_parse_u32(option, len, &lease->t2, 1); + if (r < 0) + log_debug_errno(r, "Failed to parse T2 time, ignoring: %m"); + break; + + case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: + r = lease_parse_classless_routes( + option, len, + &lease->static_route, + &lease->static_route_size); + if (r < 0) + log_debug_errno(r, "Failed to parse classless routes, ignoring: %m"); + break; + + case SD_DHCP_OPTION_NEW_TZDB_TIMEZONE: { + _cleanup_free_ char *tz = NULL; + + r = lease_parse_string(option, len, &tz); + if (r < 0) { + log_debug_errno(r, "Failed to parse timezone option, ignoring: %m"); + return 0; + } + + if (!timezone_is_valid(tz, LOG_DEBUG)) { + log_debug_errno(r, "Timezone is not valid, ignoring: %m"); + return 0; + } + + free_and_replace(lease->timezone, tz); + + break; + } + + case SD_DHCP_OPTION_VENDOR_SPECIFIC: + + if (len <= 0) + lease->vendor_specific = mfree(lease->vendor_specific); + else { + void *p; + + p = memdup(option, len); + if (!p) + return -ENOMEM; + + free(lease->vendor_specific); + lease->vendor_specific = p; + } + + lease->vendor_specific_len = len; + break; + + case SD_DHCP_OPTION_PRIVATE_BASE ... SD_DHCP_OPTION_PRIVATE_LAST: + r = dhcp_lease_insert_private_option(lease, code, option, len); + if (r < 0) + return r; + + break; + + default: + log_debug("Ignoring option DHCP option %"PRIu8" while parsing.", code); + break; + } + + return 0; +} + +/* Parses compressed domain names. */ +int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains) { + _cleanup_strv_free_ char **names = NULL; + size_t pos = 0, cnt = 0; + int r; + + assert(domains); + assert_return(option && len > 0, -ENODATA); + + while (pos < len) { + _cleanup_free_ char *name = NULL; + size_t n = 0; + size_t jump_barrier = pos, next_chunk = 0; + bool first = true; + + for (;;) { + uint8_t c; + c = option[pos++]; + + if (c == 0) { + /* End of name */ + break; + } else if (c <= 63) { + const char *label; + + /* Literal label */ + label = (const char*) (option + pos); + pos += c; + if (pos >= len) + return -EBADMSG; + + if (!GREEDY_REALLOC(name, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + if (first) + first = false; + else + name[n++] = '.'; + + r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + } else if (FLAGS_SET(c, 0xc0)) { + /* Pointer */ + + uint8_t d; + uint16_t ptr; + + if (pos >= len) + return -EBADMSG; + + d = option[pos++]; + ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; + + /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ + if (ptr >= jump_barrier) + return -EBADMSG; + jump_barrier = ptr; + + /* Save current location so we don't end up re-parsing what's parsed so far. */ + if (next_chunk == 0) + next_chunk = pos; + + pos = ptr; + } else + return -EBADMSG; + } + + if (!GREEDY_REALLOC(name, n + 1)) + return -ENOMEM; + name[n] = 0; + + r = strv_extend(&names, name); + if (r < 0) + return r; + + cnt++; + + if (next_chunk != 0) + pos = next_chunk; + } + + *domains = TAKE_PTR(names); + + return cnt; +} + +int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len) { + struct sd_dhcp_raw_option *cur, *option; + + assert(lease); + + LIST_FOREACH(options, cur, lease->private_options) { + if (tag < cur->tag) + break; + if (tag == cur->tag) { + log_debug("Ignoring duplicate option, tagged %i.", tag); + return 0; + } + } + + option = new(struct sd_dhcp_raw_option, 1); + if (!option) + return -ENOMEM; + + option->tag = tag; + option->length = len; + option->data = memdup(data, len); + if (!option->data) { + free(option); + return -ENOMEM; + } + + LIST_INSERT_BEFORE(options, lease->private_options, cur, option); + return 0; +} + +int dhcp_lease_new(sd_dhcp_lease **ret) { + sd_dhcp_lease *lease; + + lease = new0(sd_dhcp_lease, 1); + if (!lease) + return -ENOMEM; + + lease->n_ref = 1; + + *ret = lease; + return 0; +} + +int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { + _cleanup_(unlink_and_freep) char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + struct sd_dhcp_raw_option *option; + struct in_addr address; + const struct in_addr *addresses; + const void *client_id, *data; + size_t client_id_len, data_len; + char sbuf[INET_ADDRSTRLEN]; + const char *string; + uint16_t mtu; + _cleanup_free_ sd_dhcp_route **routes = NULL; + char **search_domains = NULL; + uint32_t t1, t2, lifetime; + int r; + + assert(lease); + assert(lease_file); + + r = fopen_temporary(lease_file, &f, &temp_path); + if (r < 0) + return r; + + (void) fchmod(fileno(f), 0644); + + fprintf(f, + "# This is private data. Do not parse.\n"); + + r = sd_dhcp_lease_get_address(lease, &address); + if (r >= 0) + fprintf(f, "ADDRESS=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf))); + + r = sd_dhcp_lease_get_netmask(lease, &address); + if (r >= 0) + fprintf(f, "NETMASK=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf))); + + r = sd_dhcp_lease_get_router(lease, &addresses); + if (r > 0) { + fputs("ROUTER=", f); + serialize_in_addrs(f, addresses, r, false, NULL); + fputc('\n', f); + } + + r = sd_dhcp_lease_get_server_identifier(lease, &address); + if (r >= 0) + fprintf(f, "SERVER_ADDRESS=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf))); + + r = sd_dhcp_lease_get_next_server(lease, &address); + if (r >= 0) + fprintf(f, "NEXT_SERVER=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf))); + + r = sd_dhcp_lease_get_broadcast(lease, &address); + if (r >= 0) + fprintf(f, "BROADCAST=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf))); + + r = sd_dhcp_lease_get_mtu(lease, &mtu); + if (r >= 0) + fprintf(f, "MTU=%" PRIu16 "\n", mtu); + + r = sd_dhcp_lease_get_t1(lease, &t1); + if (r >= 0) + fprintf(f, "T1=%" PRIu32 "\n", t1); + + r = sd_dhcp_lease_get_t2(lease, &t2); + if (r >= 0) + fprintf(f, "T2=%" PRIu32 "\n", t2); + + r = sd_dhcp_lease_get_lifetime(lease, &lifetime); + if (r >= 0) + fprintf(f, "LIFETIME=%" PRIu32 "\n", lifetime); + + r = sd_dhcp_lease_get_dns(lease, &addresses); + if (r > 0) { + fputs("DNS=", f); + serialize_in_addrs(f, addresses, r, false, NULL); + fputc('\n', f); + } + + r = sd_dhcp_lease_get_ntp(lease, &addresses); + if (r > 0) { + fputs("NTP=", f); + serialize_in_addrs(f, addresses, r, false, NULL); + fputc('\n', f); + } + + r = sd_dhcp_lease_get_sip(lease, &addresses); + if (r > 0) { + fputs("SIP=", f); + serialize_in_addrs(f, addresses, r, false, NULL); + fputc('\n', f); + } + + r = sd_dhcp_lease_get_domainname(lease, &string); + if (r >= 0) + fprintf(f, "DOMAINNAME=%s\n", string); + + r = sd_dhcp_lease_get_search_domains(lease, &search_domains); + if (r > 0) { + fputs("DOMAIN_SEARCH_LIST=", f); + fputstrv(f, search_domains, NULL, NULL); + fputc('\n', f); + } + + r = sd_dhcp_lease_get_hostname(lease, &string); + if (r >= 0) + fprintf(f, "HOSTNAME=%s\n", string); + + r = sd_dhcp_lease_get_root_path(lease, &string); + if (r >= 0) + fprintf(f, "ROOT_PATH=%s\n", string); + + r = sd_dhcp_lease_get_routes(lease, &routes); + if (r > 0) + serialize_dhcp_routes(f, "ROUTES", routes, r); + + r = sd_dhcp_lease_get_timezone(lease, &string); + if (r >= 0) + fprintf(f, "TIMEZONE=%s\n", string); + + r = sd_dhcp_lease_get_client_id(lease, &client_id, &client_id_len); + if (r >= 0) { + _cleanup_free_ char *client_id_hex = NULL; + + client_id_hex = hexmem(client_id, client_id_len); + if (!client_id_hex) + return -ENOMEM; + fprintf(f, "CLIENTID=%s\n", client_id_hex); + } + + r = sd_dhcp_lease_get_vendor_specific(lease, &data, &data_len); + if (r >= 0) { + _cleanup_free_ char *option_hex = NULL; + + option_hex = hexmem(data, data_len); + if (!option_hex) + return -ENOMEM; + fprintf(f, "VENDOR_SPECIFIC=%s\n", option_hex); + } + + LIST_FOREACH(options, option, lease->private_options) { + char key[STRLEN("OPTION_000")+1]; + + xsprintf(key, "OPTION_%" PRIu8, option->tag); + r = serialize_dhcp_option(f, key, option->data, option->length); + if (r < 0) + return r; + } + + r = fflush_and_check(f); + if (r < 0) + return r; + + r = conservative_rename(temp_path, lease_file); + if (r < 0) + return r; + + temp_path = mfree(temp_path); + + return 0; +} + +int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + _cleanup_free_ char + *address = NULL, + *router = NULL, + *netmask = NULL, + *server_address = NULL, + *next_server = NULL, + *broadcast = NULL, + *dns = NULL, + *ntp = NULL, + *sip = NULL, + *pop3 = NULL, + *smtp = NULL, + *lpr = NULL, + *mtu = NULL, + *routes = NULL, + *domains = NULL, + *client_id_hex = NULL, + *vendor_specific_hex = NULL, + *lifetime = NULL, + *t1 = NULL, + *t2 = NULL, + *options[SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1] = {}; + + int r, i; + + assert(lease_file); + assert(ret); + + r = dhcp_lease_new(&lease); + if (r < 0) + return r; + + r = parse_env_file(NULL, lease_file, + "ADDRESS", &address, + "ROUTER", &router, + "NETMASK", &netmask, + "SERVER_ADDRESS", &server_address, + "NEXT_SERVER", &next_server, + "BROADCAST", &broadcast, + "DNS", &dns, + "NTP", &ntp, + "SIP", &sip, + "POP3", &pop3, + "SMTP", &smtp, + "LPR", &lpr, + "MTU", &mtu, + "DOMAINNAME", &lease->domainname, + "HOSTNAME", &lease->hostname, + "DOMAIN_SEARCH_LIST", &domains, + "ROOT_PATH", &lease->root_path, + "ROUTES", &routes, + "CLIENTID", &client_id_hex, + "TIMEZONE", &lease->timezone, + "VENDOR_SPECIFIC", &vendor_specific_hex, + "LIFETIME", &lifetime, + "T1", &t1, + "T2", &t2, + "OPTION_224", &options[0], + "OPTION_225", &options[1], + "OPTION_226", &options[2], + "OPTION_227", &options[3], + "OPTION_228", &options[4], + "OPTION_229", &options[5], + "OPTION_230", &options[6], + "OPTION_231", &options[7], + "OPTION_232", &options[8], + "OPTION_233", &options[9], + "OPTION_234", &options[10], + "OPTION_235", &options[11], + "OPTION_236", &options[12], + "OPTION_237", &options[13], + "OPTION_238", &options[14], + "OPTION_239", &options[15], + "OPTION_240", &options[16], + "OPTION_241", &options[17], + "OPTION_242", &options[18], + "OPTION_243", &options[19], + "OPTION_244", &options[20], + "OPTION_245", &options[21], + "OPTION_246", &options[22], + "OPTION_247", &options[23], + "OPTION_248", &options[24], + "OPTION_249", &options[25], + "OPTION_250", &options[26], + "OPTION_251", &options[27], + "OPTION_252", &options[28], + "OPTION_253", &options[29], + "OPTION_254", &options[30]); + if (r < 0) + return r; + + if (address) { + r = inet_pton(AF_INET, address, &lease->address); + if (r <= 0) + log_debug("Failed to parse address %s, ignoring.", address); + } + + if (router) { + r = deserialize_in_addrs(&lease->router, router); + if (r < 0) + log_debug_errno(r, "Failed to deserialize router addresses %s, ignoring: %m", router); + else + lease->router_size = r; + } + + if (netmask) { + r = inet_pton(AF_INET, netmask, &lease->subnet_mask); + if (r <= 0) + log_debug("Failed to parse netmask %s, ignoring.", netmask); + else + lease->have_subnet_mask = true; + } + + if (server_address) { + r = inet_pton(AF_INET, server_address, &lease->server_address); + if (r <= 0) + log_debug("Failed to parse server address %s, ignoring.", server_address); + } + + if (next_server) { + r = inet_pton(AF_INET, next_server, &lease->next_server); + if (r <= 0) + log_debug("Failed to parse next server %s, ignoring.", next_server); + } + + if (broadcast) { + r = inet_pton(AF_INET, broadcast, &lease->broadcast); + if (r <= 0) + log_debug("Failed to parse broadcast address %s, ignoring.", broadcast); + else + lease->have_broadcast = true; + } + + if (dns) { + r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_DNS].addr, dns); + if (r < 0) + log_debug_errno(r, "Failed to deserialize DNS servers %s, ignoring: %m", dns); + else + lease->servers[SD_DHCP_LEASE_DNS].size = r; + } + + if (ntp) { + r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_NTP].addr, ntp); + if (r < 0) + log_debug_errno(r, "Failed to deserialize NTP servers %s, ignoring: %m", ntp); + else + lease->servers[SD_DHCP_LEASE_NTP].size = r; + } + + if (sip) { + r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SIP].addr, sip); + if (r < 0) + log_debug_errno(r, "Failed to deserialize SIP servers %s, ignoring: %m", sip); + else + lease->servers[SD_DHCP_LEASE_SIP].size = r; + } + + if (pop3) { + r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_POP3].addr, pop3); + if (r < 0) + log_debug_errno(r, "Failed to deserialize POP3 server %s, ignoring: %m", pop3); + else + lease->servers[SD_DHCP_LEASE_POP3].size = r; + } + + if (smtp) { + r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SMTP].addr, smtp); + if (r < 0) + log_debug_errno(r, "Failed to deserialize SMTP server %s, ignoring: %m", smtp); + else + lease->servers[SD_DHCP_LEASE_SMTP].size = r; + } + + if (lpr) { + r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_LPR].addr, lpr); + if (r < 0) + log_debug_errno(r, "Failed to deserialize LPR server %s, ignoring: %m", lpr); + else + lease->servers[SD_DHCP_LEASE_LPR].size = r; + } + + if (mtu) { + r = safe_atou16(mtu, &lease->mtu); + if (r < 0) + log_debug_errno(r, "Failed to parse MTU %s, ignoring: %m", mtu); + } + + if (domains) { + _cleanup_strv_free_ char **a = NULL; + a = strv_split(domains, " "); + if (!a) + return -ENOMEM; + + if (!strv_isempty(a)) + lease->search_domains = TAKE_PTR(a); + } + + if (routes) { + r = deserialize_dhcp_routes( + &lease->static_route, + &lease->static_route_size, + routes); + if (r < 0) + log_debug_errno(r, "Failed to parse DHCP routes %s, ignoring: %m", routes); + } + + if (lifetime) { + r = safe_atou32(lifetime, &lease->lifetime); + if (r < 0) + log_debug_errno(r, "Failed to parse lifetime %s, ignoring: %m", lifetime); + } + + if (t1) { + r = safe_atou32(t1, &lease->t1); + if (r < 0) + log_debug_errno(r, "Failed to parse T1 %s, ignoring: %m", t1); + } + + if (t2) { + r = safe_atou32(t2, &lease->t2); + if (r < 0) + log_debug_errno(r, "Failed to parse T2 %s, ignoring: %m", t2); + } + + if (client_id_hex) { + r = unhexmem(client_id_hex, SIZE_MAX, &lease->client_id, &lease->client_id_len); + if (r < 0) + log_debug_errno(r, "Failed to parse client ID %s, ignoring: %m", client_id_hex); + } + + if (vendor_specific_hex) { + r = unhexmem(vendor_specific_hex, SIZE_MAX, &lease->vendor_specific, &lease->vendor_specific_len); + if (r < 0) + log_debug_errno(r, "Failed to parse vendor specific data %s, ignoring: %m", vendor_specific_hex); + } + + for (i = 0; i <= SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE; i++) { + _cleanup_free_ void *data = NULL; + size_t len; + + if (!options[i]) + continue; + + r = unhexmem(options[i], SIZE_MAX, &data, &len); + if (r < 0) { + log_debug_errno(r, "Failed to parse private DHCP option %s, ignoring: %m", options[i]); + continue; + } + + r = dhcp_lease_insert_private_option(lease, SD_DHCP_OPTION_PRIVATE_BASE + i, data, len); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(lease); + + return 0; +} + +int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) { + struct in_addr address, mask; + int r; + + assert(lease); + + if (lease->address == 0) + return -ENODATA; + + address.s_addr = lease->address; + + /* fall back to the default subnet masks based on address class */ + r = in4_addr_default_subnet_mask(&address, &mask); + if (r < 0) + return r; + + lease->subnet_mask = mask.s_addr; + lease->have_subnet_mask = true; + + return 0; +} + +int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len) { + assert_return(lease, -EINVAL); + assert_return(client_id, -EINVAL); + assert_return(client_id_len, -EINVAL); + + if (!lease->client_id) + return -ENODATA; + + *client_id = lease->client_id; + *client_id_len = lease->client_id_len; + + return 0; +} + +int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len) { + assert_return(lease, -EINVAL); + assert_return(client_id || client_id_len <= 0, -EINVAL); + + if (client_id_len <= 0) + lease->client_id = mfree(lease->client_id); + else { + void *p; + + p = memdup(client_id, client_id_len); + if (!p) + return -ENOMEM; + + free(lease->client_id); + lease->client_id = p; + lease->client_id_len = client_id_len; + } + + return 0; +} + +int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **tz) { + assert_return(lease, -EINVAL); + assert_return(tz, -EINVAL); + + if (!lease->timezone) + return -ENODATA; + + *tz = lease->timezone; + return 0; +} + +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination) { + assert_return(route, -EINVAL); + assert_return(destination, -EINVAL); + + *destination = route->dst_addr; + return 0; +} + +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length) { + assert_return(route, -EINVAL); + assert_return(length, -EINVAL); + + *length = route->dst_prefixlen; + return 0; +} + +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway) { + assert_return(route, -EINVAL); + assert_return(gateway, -EINVAL); + + *gateway = route->gw_addr; + return 0; +} + +int sd_dhcp_route_get_option(sd_dhcp_route *route) { + assert_return(route, -EINVAL); + + return route->option; +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c new file mode 100644 index 00000000..888dfa2d --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c @@ -0,0 +1,1855 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014-2015 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include <errno.h> +#include <sys/ioctl.h> +#if 0 /* NM_IGNORED */ +#include <linux/if_arp.h> +#else /* NM_IGNORED */ +#include <net/if_arp.h> +#endif /* NM_IGNORED */ +#include <linux/if_infiniband.h> + +#include "sd-dhcp6-client.h" + +#include "alloc-util.h" +#include "dhcp-identifier.h" +#include "dhcp6-internal.h" +#include "dhcp6-lease-internal.h" +#include "dhcp6-protocol.h" +#include "dns-domain.h" +#include "event-util.h" +#include "fd-util.h" +#include "hexdecoct.h" +#include "hostname-util.h" +#include "in-addr-util.h" +#include "network-common.h" +#include "random-util.h" +#include "socket-util.h" +#include "string-table.h" +#include "strv.h" +#include "util.h" +#include "web-util.h" + +#define MAX_MAC_ADDR_LEN INFINIBAND_ALEN + +#define IRT_DEFAULT (1 * USEC_PER_DAY) +#define IRT_MINIMUM (600 * USEC_PER_SEC) + +/* what to request from the server, addresses (IA_NA) and/or prefixes (IA_PD) */ +enum { + DHCP6_REQUEST_IA_NA = 1, + DHCP6_REQUEST_IA_TA = 2, /* currently not used */ + DHCP6_REQUEST_IA_PD = 4, +}; + +struct sd_dhcp6_client { + unsigned n_ref; + + enum DHCP6State state; + sd_event *event; + int event_priority; + int ifindex; + char *ifname; + DHCP6Address hint_pd_prefix; + struct in6_addr local_address; + uint8_t mac_addr[MAX_MAC_ADDR_LEN]; + size_t mac_addr_len; + uint16_t arp_type; + DHCP6IA ia_na; + DHCP6IA ia_pd; + sd_event_source *timeout_t1; + sd_event_source *timeout_t2; + unsigned request; + be32_t transaction_id; + usec_t transaction_start; + struct sd_dhcp6_lease *lease; + int fd; + bool information_request; + bool iaid_set; + be16_t *req_opts; + size_t req_opts_len; + char *fqdn; + char *mudurl; + char **user_class; + char **vendor_class; + sd_event_source *receive_message; + usec_t retransmit_time; + uint8_t retransmit_count; + sd_event_source *timeout_resend; + sd_event_source *timeout_resend_expire; + sd_dhcp6_client_callback_t callback; + void *userdata; + struct duid duid; + size_t duid_len; + usec_t information_request_time_usec; + usec_t information_refresh_time_usec; + OrderedHashmap *extra_options; + OrderedHashmap *vendor_options; +}; + +static const uint16_t default_req_opts[] = { + SD_DHCP6_OPTION_DNS_SERVERS, + SD_DHCP6_OPTION_DOMAIN_LIST, + SD_DHCP6_OPTION_NTP_SERVER, + SD_DHCP6_OPTION_SNTP_SERVERS, +}; + +const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = { + [DHCP6_SOLICIT] = "SOLICIT", + [DHCP6_ADVERTISE] = "ADVERTISE", + [DHCP6_REQUEST] = "REQUEST", + [DHCP6_CONFIRM] = "CONFIRM", + [DHCP6_RENEW] = "RENEW", + [DHCP6_REBIND] = "REBIND", + [DHCP6_REPLY] = "REPLY", + [DHCP6_RELEASE] = "RELEASE", + [DHCP6_DECLINE] = "DECLINE", + [DHCP6_RECONFIGURE] = "RECONFIGURE", + [DHCP6_INFORMATION_REQUEST] = "INFORMATION-REQUEST", + [DHCP6_RELAY_FORW] = "RELAY-FORW", + [DHCP6_RELAY_REPL] = "RELAY-REPL", +}; + +DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int); + +const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = { + [DHCP6_STATUS_SUCCESS] = "Success", + [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure", + [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available", + [DHCP6_STATUS_NO_BINDING] = "Binding unavailable", + [DHCP6_STATUS_NOT_ON_LINK] = "Not on link", + [DHCP6_STATUS_USE_MULTICAST] = "Use multicast", + [DHCP6_STATUS_NO_PREFIX_AVAIL] = "No prefix available", + [DHCP6_STATUS_UNKNOWN_QUERY_TYPE] = "Unknown query type", + [DHCP6_STATUS_MALFORMED_QUERY] = "Malformed query", + [DHCP6_STATUS_NOT_CONFIGURED] = "Not configured", + [DHCP6_STATUS_NOT_ALLOWED] = "Not allowed", + [DHCP6_STATUS_QUERY_TERMINATED] = "Query terminated", + [DHCP6_STATUS_DATA_MISSING] = "Data missing", + [DHCP6_STATUS_CATCHUP_COMPLETE] = "Catch up complete", + [DHCP6_STATUS_NOT_SUPPORTED] = "Not supported", + [DHCP6_STATUS_TLS_CONNECTION_REFUSED] = "TLS connection refused", + [DHCP6_STATUS_ADDRESS_IN_USE] = "Address in use", + [DHCP6_STATUS_CONFIGURATION_CONFLICT] = "Configuration conflict", + [DHCP6_STATUS_MISSING_BINDING_INFORMATION] = "Missing binding information", + [DHCP6_STATUS_OUTDATED_BINDING_INFORMATION] = "Outdated binding information", + [DHCP6_STATUS_SERVER_SHUTTING_DOWN] = "Server shutting down", + [DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED] = "DNS update not supported", + [DHCP6_STATUS_EXCESSIVE_TIME_SKEW] = "Excessive time skew", +}; + +DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int); + +#define DHCP6_CLIENT_DONT_DESTROY(client) \ + _cleanup_(sd_dhcp6_client_unrefp) _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client) + +static int client_start(sd_dhcp6_client *client, enum DHCP6State state); + +int sd_dhcp6_client_set_callback( + sd_dhcp6_client *client, + sd_dhcp6_client_callback_t cb, + void *userdata) { + + assert_return(client, -EINVAL); + + client->callback = cb; + client->userdata = userdata; + + return 0; +} + +int sd_dhcp6_client_set_ifindex(sd_dhcp6_client *client, int ifindex) { + assert_return(client, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + + client->ifindex = ifindex; + return 0; +} + +int sd_dhcp6_client_set_ifname(sd_dhcp6_client *client, const char *ifname) { + assert_return(client, -EINVAL); + assert_return(ifname, -EINVAL); + + if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE)) + return -EINVAL; + + return free_and_strdup(&client->ifname, ifname); +} + +const char *sd_dhcp6_client_get_ifname(sd_dhcp6_client *client) { + if (!client) + return NULL; + + return get_ifname(client->ifindex, &client->ifname); +} + +int sd_dhcp6_client_set_local_address( + sd_dhcp6_client *client, + const struct in6_addr *local_address) { + + assert_return(client, -EINVAL); + assert_return(local_address, -EINVAL); + assert_return(in6_addr_is_link_local(local_address) > 0, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + + client->local_address = *local_address; + + return 0; +} + +int sd_dhcp6_client_set_mac( + sd_dhcp6_client *client, + const uint8_t *addr, size_t addr_len, + uint16_t arp_type) { + + assert_return(client, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(addr_len <= MAX_MAC_ADDR_LEN, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + + if (arp_type == ARPHRD_ETHER) + assert_return(addr_len == ETH_ALEN, -EINVAL); + else if (arp_type == ARPHRD_INFINIBAND) + assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); + else { + client->arp_type = ARPHRD_NONE; + client->mac_addr_len = 0; + return 0; + } + + if (client->mac_addr_len == addr_len && + memcmp(&client->mac_addr, addr, addr_len) == 0) + return 0; + + memcpy(&client->mac_addr, addr, addr_len); + client->mac_addr_len = addr_len; + client->arp_type = arp_type; + + return 0; +} + +int sd_dhcp6_client_set_prefix_delegation_hint( + sd_dhcp6_client *client, + uint8_t prefixlen, + const struct in6_addr *pd_address) { + + assert_return(client, -EINVAL); + assert_return(pd_address, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + + client->hint_pd_prefix.iapdprefix.address = *pd_address; + client->hint_pd_prefix.iapdprefix.prefixlen = prefixlen; + + return 0; +} + +int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option *v) { + int r; + + assert_return(client, -EINVAL); + assert_return(v, -EINVAL); + + r = ordered_hashmap_ensure_put(&client->vendor_options, &dhcp6_option_hash_ops, v, v); + if (r < 0) + return r; + + sd_dhcp6_option_ref(v); + + return 1; +} + +static int client_ensure_duid(sd_dhcp6_client *client) { + if (client->duid_len != 0) + return 0; + + return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); +} + +/** + * Sets DUID. If duid is non-null, the DUID is set to duid_type + duid + * without further modification. Otherwise, if duid_type is supported, DUID + * is set based on that type. Otherwise, an error is returned. + */ +static int dhcp6_client_set_duid_internal( + sd_dhcp6_client *client, + uint16_t duid_type, + const void *duid, + size_t duid_len, + usec_t llt_time) { + int r; + + assert_return(client, -EINVAL); + assert_return(duid_len == 0 || duid != NULL, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + + if (duid) { + r = dhcp_validate_duid_len(duid_type, duid_len, true); + if (r < 0) { + r = dhcp_validate_duid_len(duid_type, duid_len, false); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Failed to validate length of DUID: %m"); + + log_dhcp6_client(client, "Using DUID of type %u of incorrect length, proceeding.", duid_type); + } + + client->duid.type = htobe16(duid_type); + memcpy(&client->duid.raw.data, duid, duid_len); + client->duid_len = sizeof(client->duid.type) + duid_len; + } else +#if 0 /* NM_IGNORED */ + switch (duid_type) { + case DUID_TYPE_LLT: + if (client->mac_addr_len == 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LLT, MAC address is not set."); + + r = dhcp_identifier_set_duid_llt(&client->duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Failed to set DUID-LLT: %m"); + break; + case DUID_TYPE_EN: + r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Failed to set DUID-EN: %m"); + break; + case DUID_TYPE_LL: + if (client->mac_addr_len == 0) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EOPNOTSUPP), "Failed to set DUID-LL, MAC address is not set."); + + r = dhcp_identifier_set_duid_ll(&client->duid, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Failed to set DUID-LL: %m"); + break; + case DUID_TYPE_UUID: + r = dhcp_identifier_set_duid_uuid(&client->duid, &client->duid_len); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Failed to set DUID-UUID: %m"); + break; + default: + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Invalid DUID type"); + } +#else /* NM_IGNORED */ + g_return_val_if_reached (-EINVAL); +#endif /* NM_IGNORED */ + + return 0; +} + +int sd_dhcp6_client_set_duid( + sd_dhcp6_client *client, + uint16_t duid_type, + const void *duid, + size_t duid_len) { + return dhcp6_client_set_duid_internal(client, duid_type, duid, duid_len, 0); +} + +int sd_dhcp6_client_set_duid_llt( + sd_dhcp6_client *client, + usec_t llt_time) { + return dhcp6_client_set_duid_internal(client, DUID_TYPE_LLT, NULL, 0, llt_time); +} + +static const char* const dhcp6_duid_type_table[_DUID_TYPE_MAX] = { + [DUID_TYPE_LLT] = "DUID-LLT", + [DUID_TYPE_EN] = "DUID-EN/Vendor", + [DUID_TYPE_LL] = "DUID-LL", + [DUID_TYPE_UUID] = "UUID", +}; +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_duid_type, DUIDType); + +int sd_dhcp6_client_duid_as_string( + sd_dhcp6_client *client, + char **duid) { + _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL; + const char *v; + int r; + + assert_return(client, -EINVAL); + assert_return(client->duid_len > 0, -ENODATA); + assert_return(duid, -EINVAL); + + v = dhcp6_duid_type_to_string(be16toh(client->duid.type)); + if (v) { + s = strdup(v); + if (!s) + return -ENOMEM; + } else { + r = asprintf(&s, "%0x", client->duid.type); + if (r < 0) + return -ENOMEM; + } + + t = hexmem(&client->duid.raw.data, client->duid_len); + if (!t) + return -ENOMEM; + + p = strjoin(s, ":", t); + if (!p) + return -ENOMEM; + + *duid = TAKE_PTR(p); + + return 0; +} + +int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) { + assert_return(client, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + + client->ia_na.ia_na.id = htobe32(iaid); + client->ia_pd.ia_pd.id = htobe32(iaid); + client->iaid_set = true; + + return 0; +} + +int sd_dhcp6_client_get_iaid(sd_dhcp6_client *client, uint32_t *iaid) { + assert_return(client, -EINVAL); + assert_return(iaid, -EINVAL); + + if (!client->iaid_set) + return -ENODATA; + + *iaid = be32toh(client->ia_na.ia_na.id); + + return 0; +} + +int sd_dhcp6_client_set_fqdn( + sd_dhcp6_client *client, + const char *fqdn) { + + assert_return(client, -EINVAL); + + /* Make sure FQDN qualifies as DNS and as Linux hostname */ + if (fqdn && + !(hostname_is_valid(fqdn, 0) && dns_name_is_valid(fqdn) > 0)) + return -EINVAL; + + return free_and_strdup(&client->fqdn, fqdn); +} + +int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled) { + assert_return(client, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + + client->information_request = enabled; + + return 0; +} + +int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enabled) { + assert_return(client, -EINVAL); + assert_return(enabled, -EINVAL); + + *enabled = client->information_request; + + return 0; +} + +int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option) { + size_t t; + + assert_return(client, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + + if (option <= 0 || option >= UINT8_MAX) + return -EINVAL; + + for (t = 0; t < client->req_opts_len; t++) + if (client->req_opts[t] == htobe16(option)) + return -EEXIST; + + if (!GREEDY_REALLOC(client->req_opts, client->req_opts_len + 1)) + return -ENOMEM; + + client->req_opts[client->req_opts_len++] = htobe16(option); + + return 0; +} + +int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client *client, const char *mudurl) { + assert_return(client, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(mudurl, -EINVAL); + assert_return(strlen(mudurl) <= UINT8_MAX, -EINVAL); + assert_return(http_url_is_valid(mudurl), -EINVAL); + + return free_and_strdup(&client->mudurl, mudurl); +} + +int sd_dhcp6_client_set_request_user_class(sd_dhcp6_client *client, char * const *user_class) { + char * const *p; + char **s; + + assert_return(client, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(!strv_isempty(user_class), -EINVAL); + + STRV_FOREACH(p, user_class) { + size_t len = strlen(*p); + + if (len > UINT16_MAX || len == 0) + return -EINVAL; + } + + s = strv_copy(user_class); + if (!s) + return -ENOMEM; + + return strv_free_and_replace(client->user_class, s); +} + +int sd_dhcp6_client_set_request_vendor_class(sd_dhcp6_client *client, char * const *vendor_class) { + char * const *p; + char **s; + + assert_return(client, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(!strv_isempty(vendor_class), -EINVAL); + + STRV_FOREACH(p, vendor_class) { + size_t len = strlen(*p); + + if (len > UINT16_MAX || len == 0) + return -EINVAL; + } + + s = strv_copy(vendor_class); + if (!s) + return -ENOMEM; + + return strv_free_and_replace(client->vendor_class, s); +} + +int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, int *delegation) { + assert_return(client, -EINVAL); + assert_return(delegation, -EINVAL); + + *delegation = FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD); + + return 0; +} + +int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, int delegation) { + assert_return(client, -EINVAL); + + SET_FLAG(client->request, DHCP6_REQUEST_IA_PD, delegation); + + return 0; +} + +int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client, int *request) { + assert_return(client, -EINVAL); + assert_return(request, -EINVAL); + + *request = FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA); + + return 0; +} + +int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, int request) { + assert_return(client, -EINVAL); + + SET_FLAG(client->request, DHCP6_REQUEST_IA_NA, request); + + return 0; +} + +int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id) { + assert_return(client, -EINVAL); + + client->transaction_id = transaction_id; + + return 0; +} + +int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) { + assert_return(client, -EINVAL); + + if (!client->lease) + return -ENOMSG; + + if (ret) + *ret = client->lease; + + return 0; +} + +int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v) { + int r; + + assert_return(client, -EINVAL); + assert_return(v, -EINVAL); + + r = ordered_hashmap_ensure_put(&client->extra_options, &dhcp6_option_hash_ops, UINT_TO_PTR(v->option), v); + if (r < 0) + return r; + + sd_dhcp6_option_ref(v); + return 0; +} + +static void client_notify(sd_dhcp6_client *client, int event) { + assert(client); + + if (client->callback) + client->callback(client, event, client->userdata); +} + +static int client_reset(sd_dhcp6_client *client) { + assert(client); + + client->lease = sd_dhcp6_lease_unref(client->lease); + + client->receive_message = + sd_event_source_unref(client->receive_message); + + client->transaction_id = 0; + client->transaction_start = 0; + + client->retransmit_time = 0; + client->retransmit_count = 0; + + (void) event_source_disable(client->timeout_resend); + (void) event_source_disable(client->timeout_resend_expire); + (void) event_source_disable(client->timeout_t1); + (void) event_source_disable(client->timeout_t2); + + client->state = DHCP6_STATE_STOPPED; + + return 0; +} + +static void client_stop(sd_dhcp6_client *client, int error) { + DHCP6_CLIENT_DONT_DESTROY(client); + + assert(client); + + client_notify(client, error); + + client_reset(client); +} + +static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { + _cleanup_free_ DHCP6Message *message = NULL; + struct in6_addr all_servers = + IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT; + struct sd_dhcp6_option *j; + size_t len, optlen = 512; + uint8_t *opt; + int r; + usec_t elapsed_usec; + be16_t elapsed_time; + + assert(client); + + len = sizeof(DHCP6Message) + optlen; + + message = malloc0(len); + if (!message) + return -ENOMEM; + + opt = (uint8_t *)(message + 1); + + message->transaction_id = client->transaction_id; + + switch(client->state) { + case DHCP6_STATE_INFORMATION_REQUEST: + message->type = DHCP6_INFORMATION_REQUEST; + + if (client->mudurl) { + r = dhcp6_option_append(&opt, &optlen, + SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl), + client->mudurl); + if (r < 0) + return r; + } + + break; + + case DHCP6_STATE_SOLICITATION: + message->type = DHCP6_SOLICIT; + + r = dhcp6_option_append(&opt, &optlen, + SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL); + if (r < 0) + return r; + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) { + r = dhcp6_option_append_ia(&opt, &optlen, + &client->ia_na); + if (r < 0) + return r; + } + + if (client->fqdn) { + r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn); + if (r < 0) + return r; + } + + if (client->mudurl) { + r = dhcp6_option_append(&opt, &optlen, + SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl), + client->mudurl); + if (r < 0) + return r; + } + + if (client->user_class) { + r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class); + if (r < 0) + return r; + } + + if (client->vendor_class) { + r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class); + if (r < 0) + return r; + } + + if (!ordered_hashmap_isempty(client->vendor_options)) { + r = dhcp6_option_append_vendor_option(&opt, &optlen, + client->vendor_options); + if (r < 0) + return r; + } + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) { + r = dhcp6_option_append_pd(&opt, &optlen, &client->ia_pd, &client->hint_pd_prefix); + if (r < 0) + return r; + } + + break; + + case DHCP6_STATE_REQUEST: + case DHCP6_STATE_RENEW: + + if (client->state == DHCP6_STATE_REQUEST) + message->type = DHCP6_REQUEST; + else + message->type = DHCP6_RENEW; + + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_SERVERID, + client->lease->serverid_len, + client->lease->serverid); + if (r < 0) + return r; + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA) && client->lease->ia.addresses) { + r = dhcp6_option_append_ia(&opt, &optlen, + &client->lease->ia); + if (r < 0) + return r; + } + + if (client->fqdn) { + r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn); + if (r < 0) + return r; + } + + if (client->mudurl) { + r = dhcp6_option_append(&opt, &optlen, + SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl), + client->mudurl); + if (r < 0) + return r; + } + + if (client->user_class) { + r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class); + if (r < 0) + return r; + } + + if (client->vendor_class) { + r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class); + if (r < 0) + return r; + } + + if (!ordered_hashmap_isempty(client->vendor_options)) { + r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options); + if (r < 0) + return r; + } + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) { + r = dhcp6_option_append_pd(&opt, &optlen, &client->lease->pd, NULL); + if (r < 0) + return r; + } + + break; + + case DHCP6_STATE_REBIND: + message->type = DHCP6_REBIND; + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) { + r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia); + if (r < 0) + return r; + } + + if (client->fqdn) { + r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn); + if (r < 0) + return r; + } + + if (client->mudurl) { + r = dhcp6_option_append(&opt, &optlen, + SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl), + client->mudurl); + if (r < 0) + return r; + } + + if (client->user_class) { + r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class); + if (r < 0) + return r; + } + + if (client->vendor_class) { + r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class); + if (r < 0) + return r; + } + + if (!ordered_hashmap_isempty(client->vendor_options)) { + r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options); + if (r < 0) + return r; + } + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) { + r = dhcp6_option_append_pd(&opt, &optlen, &client->lease->pd, NULL); + if (r < 0) + return r; + } + + break; + + case DHCP6_STATE_STOPPED: + case DHCP6_STATE_BOUND: + return -EINVAL; + } + + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ORO, + client->req_opts_len * sizeof(be16_t), + client->req_opts); + if (r < 0) + return r; + + assert(client->duid_len); + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_CLIENTID, + client->duid_len, &client->duid); + if (r < 0) + return r; + + elapsed_usec = time_now - client->transaction_start; + if (elapsed_usec < 0xffff * USEC_PER_MSEC * 10) + elapsed_time = htobe16(elapsed_usec / USEC_PER_MSEC / 10); + else + elapsed_time = 0xffff; + + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ELAPSED_TIME, + sizeof(elapsed_time), &elapsed_time); + if (r < 0) + return r; + + ORDERED_HASHMAP_FOREACH(j, client->extra_options) { + r = dhcp6_option_append(&opt, &optlen, j->option, j->length, j->data); + if (r < 0) + return r; + } + + r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message, + len - optlen); + if (r < 0) + return r; + + log_dhcp6_client(client, "Sent %s", + dhcp6_message_type_to_string(message->type)); + + return 0; +} + +static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp6_client *client = userdata; + + assert(s); + assert(client); + assert(client->lease); + + (void) event_source_disable(client->timeout_t2); + + log_dhcp6_client(client, "Timeout T2"); + + client_start(client, DHCP6_STATE_REBIND); + + return 0; +} + +static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp6_client *client = userdata; + + assert(s); + assert(client); + assert(client->lease); + + (void) event_source_disable(client->timeout_t1); + + log_dhcp6_client(client, "Timeout T1"); + + client_start(client, DHCP6_STATE_RENEW); + + return 0; +} + +static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp6_client *client = userdata; + DHCP6_CLIENT_DONT_DESTROY(client); + enum DHCP6State state; + + assert(s); + assert(client); + assert(client->event); + + state = client->state; + + client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE); + + /* RFC 3315, section 18.1.4., says that "...the client may choose to + use a Solicit message to locate a new DHCP server..." */ + if (state == DHCP6_STATE_REBIND) + client_start(client, DHCP6_STATE_SOLICITATION); + + return 0; +} + +static usec_t client_timeout_compute_random(usec_t val) { + return val - (random_u32() % USEC_PER_SEC) * val / 10 / USEC_PER_SEC; +} + +static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userdata) { + int r = 0; + sd_dhcp6_client *client = userdata; + usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0; + usec_t max_retransmit_duration = 0; + uint8_t max_retransmit_count = 0; + char time_string[FORMAT_TIMESPAN_MAX]; + + assert(s); + assert(client); + assert(client->event); + + (void) event_source_disable(client->timeout_resend); + + switch (client->state) { + case DHCP6_STATE_INFORMATION_REQUEST: + init_retransmit_time = DHCP6_INF_TIMEOUT; + max_retransmit_time = DHCP6_INF_MAX_RT; + + break; + + case DHCP6_STATE_SOLICITATION: + + if (client->retransmit_count && client->lease) { + client_start(client, DHCP6_STATE_REQUEST); + return 0; + } + + init_retransmit_time = DHCP6_SOL_TIMEOUT; + max_retransmit_time = DHCP6_SOL_MAX_RT; + + break; + + case DHCP6_STATE_REQUEST: + init_retransmit_time = DHCP6_REQ_TIMEOUT; + max_retransmit_time = DHCP6_REQ_MAX_RT; + max_retransmit_count = DHCP6_REQ_MAX_RC; + + break; + + case DHCP6_STATE_RENEW: + init_retransmit_time = DHCP6_REN_TIMEOUT; + max_retransmit_time = DHCP6_REN_MAX_RT; + + /* RFC 3315, section 18.1.3. says max retransmit duration will + be the remaining time until T2. Instead of setting MRD, + wait for T2 to trigger with the same end result */ + + break; + + case DHCP6_STATE_REBIND: + init_retransmit_time = DHCP6_REB_TIMEOUT; + max_retransmit_time = DHCP6_REB_MAX_RT; + + if (event_source_is_enabled(client->timeout_resend_expire) <= 0) { + uint32_t expire = 0; + + r = dhcp6_lease_ia_rebind_expire(&client->lease->ia, &expire); + if (r < 0) { + client_stop(client, r); + return 0; + } + max_retransmit_duration = expire * USEC_PER_SEC; + } + + break; + + case DHCP6_STATE_STOPPED: + case DHCP6_STATE_BOUND: + return 0; + } + + if (max_retransmit_count > 0 && + client->retransmit_count >= max_retransmit_count) { + client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX); + return 0; + } + + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + goto error; + + r = client_send_message(client, time_now); + if (r >= 0) + client->retransmit_count++; + + if (client->retransmit_time == 0) { + client->retransmit_time = + client_timeout_compute_random(init_retransmit_time); + + if (client->state == DHCP6_STATE_SOLICITATION) + client->retransmit_time += init_retransmit_time / 10; + + } else { + if (max_retransmit_time > 0 && + client->retransmit_time > max_retransmit_time / 2) + client->retransmit_time = client_timeout_compute_random(max_retransmit_time); + else + client->retransmit_time += client_timeout_compute_random(client->retransmit_time); + } + + log_dhcp6_client(client, "Next retransmission in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->retransmit_time, USEC_PER_SEC)); + + r = event_reset_time(client->event, &client->timeout_resend, + clock_boottime_or_monotonic(), + time_now + client->retransmit_time, 10 * USEC_PER_MSEC, + client_timeout_resend, client, + client->event_priority, "dhcp6-resend-timer", true); + if (r < 0) + goto error; + + if (max_retransmit_duration > 0 && event_source_is_enabled(client->timeout_resend_expire) <= 0) { + + log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs", + max_retransmit_duration / USEC_PER_SEC); + + r = event_reset_time(client->event, &client->timeout_resend_expire, + clock_boottime_or_monotonic(), + time_now + max_retransmit_duration, USEC_PER_SEC, + client_timeout_resend_expire, client, + client->event_priority, "dhcp6-resend-expire-timer", true); + if (r < 0) + goto error; + } + +error: + if (r < 0) + client_stop(client, r); + + return 0; +} + +static int client_ensure_iaid(sd_dhcp6_client *client) { + int r; + uint32_t iaid; + + assert(client); + + if (client->iaid_set) + return 0; + + r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, true, &iaid); + if (r < 0) + return r; + + client->ia_na.ia_na.id = iaid; + client->ia_pd.ia_pd.id = iaid; + client->iaid_set = true; + + return 0; +} + +static int client_parse_message( + sd_dhcp6_client *client, + DHCP6Message *message, + size_t len, + sd_dhcp6_lease *lease) { + + uint16_t ia_na_status = 0, ia_pd_status = 0; + uint32_t lt_t1 = ~0, lt_t2 = ~0; + usec_t irt = IRT_DEFAULT; + bool clientid = false; + size_t pos = 0; + int r; + + assert(client); + assert(message); + assert(len >= sizeof(DHCP6Message)); + assert(lease); + + len -= sizeof(DHCP6Message); + + while (pos < len) { + DHCP6Option *option = (DHCP6Option *) &message->options[pos]; + uint16_t optcode, optlen; + be32_t iaid_lease; + int status; + uint8_t *optval; + + if (len < pos + offsetof(DHCP6Option, data)) + return -ENOBUFS; + + optcode = be16toh(option->code); + optlen = be16toh(option->len); + optval = option->data; + + if (len < pos + offsetof(DHCP6Option, data) + optlen) + return -ENOBUFS; + + switch (optcode) { + case SD_DHCP6_OPTION_CLIENTID: + if (clientid) { + log_dhcp6_client(client, "%s contains multiple clientids", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + if (optlen != client->duid_len || + memcmp(&client->duid, optval, optlen) != 0) { + log_dhcp6_client(client, "%s DUID does not match", + dhcp6_message_type_to_string(message->type)); + + return -EINVAL; + } + clientid = true; + + break; + + case SD_DHCP6_OPTION_SERVERID: + r = dhcp6_lease_get_serverid(lease, NULL, NULL); + if (r >= 0) { + log_dhcp6_client(client, "%s contains multiple serverids", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + r = dhcp6_lease_set_serverid(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_PREFERENCE: + if (optlen != 1) + return -EINVAL; + + r = dhcp6_lease_set_preference(lease, optval[0]); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_STATUS_CODE: + status = dhcp6_option_parse_status(option, optlen + sizeof(DHCP6Option)); + if (status < 0) + return status; + + if (status > 0) { + log_dhcp6_client(client, "%s Status %s", + dhcp6_message_type_to_string(message->type), + dhcp6_message_status_to_string(status)); + + return -EINVAL; + } + + break; + + case SD_DHCP6_OPTION_IA_NA: + if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { + log_dhcp6_client(client, "Information request ignoring IA NA option"); + + break; + } + + r = dhcp6_option_parse_ia(client, option, &lease->ia, &ia_na_status); + if (r < 0 && r != -ENOMSG) + return r; + + if (ia_na_status == DHCP6_STATUS_NO_ADDRS_AVAIL) { + pos += offsetof(DHCP6Option, data) + optlen; + continue; + } + + r = dhcp6_lease_get_iaid(lease, &iaid_lease); + if (r < 0) + return r; + + if (client->ia_na.ia_na.id != iaid_lease) { + log_dhcp6_client(client, "%s has wrong IAID for IA NA", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + if (lease->ia.addresses) { + lt_t1 = MIN(lt_t1, be32toh(lease->ia.ia_na.lifetime_t1)); + lt_t2 = MIN(lt_t2, be32toh(lease->ia.ia_na.lifetime_t1)); + } + + break; + + case SD_DHCP6_OPTION_IA_PD: + if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { + log_dhcp6_client(client, "Information request ignoring IA PD option"); + + break; + } + + r = dhcp6_option_parse_ia(client, option, &lease->pd, &ia_pd_status); + if (r < 0 && r != -ENOMSG) + return r; + + if (ia_pd_status == DHCP6_STATUS_NO_PREFIX_AVAIL) { + pos += offsetof(DHCP6Option, data) + optlen; + continue; + } + + r = dhcp6_lease_get_pd_iaid(lease, &iaid_lease); + if (r < 0) + return r; + + if (client->ia_pd.ia_pd.id != iaid_lease) { + log_dhcp6_client(client, "%s has wrong IAID for IA PD", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + if (lease->pd.addresses) { + lt_t1 = MIN(lt_t1, be32toh(lease->pd.ia_pd.lifetime_t1)); + lt_t2 = MIN(lt_t2, be32toh(lease->pd.ia_pd.lifetime_t2)); + } + + break; + + case SD_DHCP6_OPTION_RAPID_COMMIT: + r = dhcp6_lease_set_rapid_commit(lease); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_DNS_SERVERS: + r = dhcp6_lease_set_dns(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_DOMAIN_LIST: + r = dhcp6_lease_set_domains(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_NTP_SERVER: + r = dhcp6_lease_set_ntp(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_SNTP_SERVERS: + r = dhcp6_lease_set_sntp(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_FQDN: + r = dhcp6_lease_set_fqdn(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME: + if (optlen != 4) + return -EINVAL; + + irt = unaligned_read_be32((be32_t *) optval) * USEC_PER_SEC; + break; + } + + pos += offsetof(DHCP6Option, data) + optlen; + } + + if (ia_na_status > 0 && ia_pd_status > 0) { + log_dhcp6_client(client, "No IA_PD prefix or IA_NA address received. Ignoring."); + return -EINVAL; + } + + if (!clientid) { + log_dhcp6_client(client, "%s has incomplete options", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + if (client->state != DHCP6_STATE_INFORMATION_REQUEST) { + r = dhcp6_lease_get_serverid(lease, NULL, NULL); + if (r < 0) { + log_dhcp6_client(client, "%s has no server id", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + } else { + if (lease->ia.addresses) { + lease->ia.ia_na.lifetime_t1 = htobe32(lt_t1); + lease->ia.ia_na.lifetime_t2 = htobe32(lt_t2); + } + + if (lease->pd.addresses) { + lease->pd.ia_pd.lifetime_t1 = htobe32(lt_t1); + lease->pd.ia_pd.lifetime_t2 = htobe32(lt_t2); + } + } + + client->information_refresh_time_usec = MAX(irt, IRT_MINIMUM); + + return 0; +} + +static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply, size_t len) { + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; + bool rapid_commit; + int r; + + assert(client); + assert(reply); + + if (reply->type != DHCP6_REPLY) + return 0; + + r = dhcp6_lease_new(&lease); + if (r < 0) + return -ENOMEM; + + r = client_parse_message(client, reply, len, lease); + if (r < 0) + return r; + + if (client->state == DHCP6_STATE_SOLICITATION) { + r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit); + if (r < 0) + return r; + + if (!rapid_commit) + return 0; + } + + sd_dhcp6_lease_unref(client->lease); + client->lease = TAKE_PTR(lease); + + return DHCP6_STATE_BOUND; +} + +static int client_receive_advertise(sd_dhcp6_client *client, DHCP6Message *advertise, size_t len) { + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; + uint8_t pref_advertise = 0, pref_lease = 0; + int r; + + if (advertise->type != DHCP6_ADVERTISE) + return 0; + + r = dhcp6_lease_new(&lease); + if (r < 0) + return r; + + r = client_parse_message(client, advertise, len, lease); + if (r < 0) + return r; + + r = dhcp6_lease_get_preference(lease, &pref_advertise); + if (r < 0) + return r; + + r = dhcp6_lease_get_preference(client->lease, &pref_lease); + + if (r < 0 || pref_advertise > pref_lease) { + sd_dhcp6_lease_unref(client->lease); + client->lease = TAKE_PTR(lease); + r = 0; + } + + if (pref_advertise == 255 || client->retransmit_count > 1) + r = DHCP6_STATE_REQUEST; + + return r; +} + +static int client_receive_message( + sd_event_source *s, + int fd, uint32_t + revents, + void *userdata) { + + sd_dhcp6_client *client = userdata; + DHCP6_CLIENT_DONT_DESTROY(client); + _cleanup_free_ DHCP6Message *message = NULL; + ssize_t buflen, len; + int r = 0; + + assert(s); + assert(client); + assert(client->event); + + buflen = next_datagram_size_fd(fd); + if (buflen == -ENETDOWN) + /* the link is down. Don't return an error or the I/O event + source will be disconnected and we won't be able to receive + packets again when the link comes back. */ + return 0; + if (buflen < 0) + return buflen; + + message = malloc(buflen); + if (!message) + return -ENOMEM; + + len = recv(fd, message, buflen, 0); + if (len < 0) { + /* see comment above for why we shouldn't error out on ENETDOWN. */ + if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN)) + return 0; + + return log_dhcp6_client_errno(client, errno, "Could not receive message from UDP socket: %m"); + + } + if ((size_t) len < sizeof(DHCP6Message)) { + log_dhcp6_client(client, "Too small to be DHCP6 message: ignoring"); + return 0; + } + + switch(message->type) { + case DHCP6_SOLICIT: + case DHCP6_REQUEST: + case DHCP6_CONFIRM: + case DHCP6_RENEW: + case DHCP6_REBIND: + case DHCP6_RELEASE: + case DHCP6_DECLINE: + case DHCP6_INFORMATION_REQUEST: + case DHCP6_RELAY_FORW: + case DHCP6_RELAY_REPL: + return 0; + + case DHCP6_ADVERTISE: + case DHCP6_REPLY: + case DHCP6_RECONFIGURE: + break; + + default: + log_dhcp6_client(client, "Unknown message type %d", message->type); + return 0; + } + + if (client->transaction_id != (message->transaction_id & + htobe32(0x00ffffff))) + return 0; + + switch (client->state) { + case DHCP6_STATE_INFORMATION_REQUEST: + r = client_receive_reply(client, message, len); + if (r < 0) + return 0; + + client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST); + + client_start(client, DHCP6_STATE_STOPPED); + + break; + + case DHCP6_STATE_SOLICITATION: + r = client_receive_advertise(client, message, len); + + if (r == DHCP6_STATE_REQUEST) { + client_start(client, r); + + break; + } + + _fallthrough_; /* for Solicitation Rapid Commit option check */ + case DHCP6_STATE_REQUEST: + case DHCP6_STATE_RENEW: + case DHCP6_STATE_REBIND: + + r = client_receive_reply(client, message, len); + if (r < 0) + return 0; + + if (r == DHCP6_STATE_BOUND) { + + r = client_start(client, DHCP6_STATE_BOUND); + if (r < 0) { + client_stop(client, r); + return 0; + } + + client_notify(client, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE); + } + + break; + + case DHCP6_STATE_BOUND: + + break; + + case DHCP6_STATE_STOPPED: + return 0; + } + + log_dhcp6_client(client, "Recv %s", + dhcp6_message_type_to_string(message->type)); + + return 0; +} + +static int client_get_lifetime(sd_dhcp6_client *client, uint32_t *lifetime_t1, + uint32_t *lifetime_t2) { + assert_return(client, -EINVAL); + assert_return(client->lease, -EINVAL); + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA) && client->lease->ia.addresses) { + *lifetime_t1 = be32toh(client->lease->ia.ia_na.lifetime_t1); + *lifetime_t2 = be32toh(client->lease->ia.ia_na.lifetime_t2); + + return 0; + } + + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) { + *lifetime_t1 = be32toh(client->lease->pd.ia_pd.lifetime_t1); + *lifetime_t2 = be32toh(client->lease->pd.ia_pd.lifetime_t2); + + return 0; + } + + return -ENOMSG; +} + +static int client_start(sd_dhcp6_client *client, enum DHCP6State state) { + int r; + usec_t timeout, time_now; + char time_string[FORMAT_TIMESPAN_MAX]; + uint32_t lifetime_t1, lifetime_t2; + + assert_return(client, -EINVAL); + assert_return(client->event, -EINVAL); + assert_return(client->ifindex > 0, -EINVAL); + assert_return(client->state != state, -EINVAL); + + (void) event_source_disable(client->timeout_resend_expire); + (void) event_source_disable(client->timeout_resend); + client->retransmit_time = 0; + client->retransmit_count = 0; + + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; + + if (!client->receive_message) { + r = sd_event_add_io(client->event, &client->receive_message, + client->fd, EPOLLIN, client_receive_message, + client); + if (r < 0) + goto error; + + r = sd_event_source_set_priority(client->receive_message, + client->event_priority); + if (r < 0) + goto error; + + r = sd_event_source_set_description(client->receive_message, + "dhcp6-receive-message"); + if (r < 0) + goto error; + } + + switch (state) { + case DHCP6_STATE_STOPPED: + if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { + client->state = DHCP6_STATE_STOPPED; + + return 0; + } + + _fallthrough_; + case DHCP6_STATE_SOLICITATION: + client->state = DHCP6_STATE_SOLICITATION; + + break; + + case DHCP6_STATE_INFORMATION_REQUEST: + case DHCP6_STATE_REQUEST: + case DHCP6_STATE_RENEW: + case DHCP6_STATE_REBIND: + + client->state = state; + + break; + + case DHCP6_STATE_BOUND: + + r = client_get_lifetime(client, &lifetime_t1, &lifetime_t2); + if (r < 0) + goto error; + + if (lifetime_t1 == 0xffffffff || lifetime_t2 == 0xffffffff) { + log_dhcp6_client(client, "Infinite T1 0x%08x or T2 0x%08x", + lifetime_t1, lifetime_t2); + + return 0; + } + + timeout = client_timeout_compute_random(lifetime_t1 * USEC_PER_SEC); + + log_dhcp6_client(client, "T1 expires in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC)); + + r = event_reset_time(client->event, &client->timeout_t1, + clock_boottime_or_monotonic(), + time_now + timeout, 10 * USEC_PER_SEC, + client_timeout_t1, client, + client->event_priority, "dhcp6-t1-timeout", true); + if (r < 0) + goto error; + + timeout = client_timeout_compute_random(lifetime_t2 * USEC_PER_SEC); + + log_dhcp6_client(client, "T2 expires in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC)); + + r = event_reset_time(client->event, &client->timeout_t2, + clock_boottime_or_monotonic(), + time_now + timeout, 10 * USEC_PER_SEC, + client_timeout_t2, client, + client->event_priority, "dhcp6-t2-timeout", true); + if (r < 0) + goto error; + + client->state = state; + + return 0; + } + + client->transaction_id = random_u32() & htobe32(0x00ffffff); + client->transaction_start = time_now; + + r = event_reset_time(client->event, &client->timeout_resend, + clock_boottime_or_monotonic(), + 0, 0, + client_timeout_resend, client, + client->event_priority, "dhcp6-resend-timeout", true); + if (r < 0) + goto error; + + return 0; + + error: + client_reset(client); + return r; +} + +int sd_dhcp6_client_stop(sd_dhcp6_client *client) { + if (!client) + return 0; + + client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP); + + client->fd = safe_close(client->fd); + + return 0; +} + +int sd_dhcp6_client_is_running(sd_dhcp6_client *client) { + assert_return(client, -EINVAL); + + return client->state != DHCP6_STATE_STOPPED; +} + +int sd_dhcp6_client_start(sd_dhcp6_client *client) { + enum DHCP6State state = DHCP6_STATE_SOLICITATION; + int r; + + assert_return(client, -EINVAL); + assert_return(client->event, -EINVAL); + assert_return(client->ifindex > 0, -EINVAL); + assert_return(in6_addr_is_link_local(&client->local_address) > 0, -EINVAL); + + if (client->state != DHCP6_STATE_STOPPED) + return -EBUSY; + + if (!client->information_request && !client->request) + return -EINVAL; + + r = client_reset(client); + if (r < 0) + return r; + + r = client_ensure_iaid(client); + if (r < 0) + return r; + + r = client_ensure_duid(client); + if (r < 0) + return r; + + if (client->fd < 0) { + r = dhcp6_network_bind_udp_socket(client->ifindex, &client->local_address); + if (r < 0) { + _cleanup_free_ char *p = NULL; + + (void) in_addr_to_string(AF_INET6, (const union in_addr_union*) &client->local_address, &p); + return log_dhcp6_client_errno(client, r, + "Failed to bind to UDP socket at address %s: %m", strna(p)); + } + + client->fd = r; + } + + if (client->information_request) { + usec_t t = now(CLOCK_MONOTONIC); + + if (t < usec_add(client->information_request_time_usec, client->information_refresh_time_usec)) + return 0; + + client->information_request_time_usec = t; + state = DHCP6_STATE_INFORMATION_REQUEST; + } + + log_dhcp6_client(client, "Started in %s mode", + client->information_request ? "Information request" : "Managed"); + + return client_start(client, state); +} + +int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64_t priority) { + int r; + + assert_return(client, -EINVAL); + assert_return(!client->event, -EBUSY); + + if (event) + client->event = sd_event_ref(event); + else { + r = sd_event_default(&client->event); + if (r < 0) + return 0; + } + + client->event_priority = priority; + + return 0; +} + +int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) { + assert_return(client, -EINVAL); + + client->event = sd_event_unref(client->event); + + return 0; +} + +sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) { + assert_return(client, NULL); + + return client->event; +} + +static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) { + assert(client); + + client->timeout_resend = sd_event_source_unref(client->timeout_resend); + client->timeout_resend_expire = sd_event_source_unref(client->timeout_resend_expire); + client->timeout_t1 = sd_event_source_unref(client->timeout_t1); + client->timeout_t2 = sd_event_source_unref(client->timeout_t2); + + client_reset(client); + + client->fd = safe_close(client->fd); + + sd_dhcp6_client_detach_event(client); + + free(client->req_opts); + free(client->fqdn); + free(client->mudurl); + + ordered_hashmap_free(client->extra_options); + strv_free(client->user_class); + strv_free(client->vendor_class); + free(client->ifname); + + return mfree(client); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_client, sd_dhcp6_client, dhcp6_client_free); + +int sd_dhcp6_client_new(sd_dhcp6_client **ret) { + _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL; + _cleanup_free_ be16_t *req_opts = NULL; + size_t t; + + assert_return(ret, -EINVAL); + + req_opts = new(be16_t, ELEMENTSOF(default_req_opts)); + if (!req_opts) + return -ENOMEM; + + for (t = 0; t < ELEMENTSOF(default_req_opts); t++) + req_opts[t] = htobe16(default_req_opts[t]); + + client = new(sd_dhcp6_client, 1); + if (!client) + return -ENOMEM; + + *client = (sd_dhcp6_client) { + .n_ref = 1, + .ia_na.type = SD_DHCP6_OPTION_IA_NA, + .ia_pd.type = SD_DHCP6_OPTION_IA_PD, + .ifindex = -1, + .request = DHCP6_REQUEST_IA_NA, + .fd = -1, + .req_opts_len = ELEMENTSOF(default_req_opts), + .hint_pd_prefix.iapdprefix.lifetime_preferred = (be32_t) -1, + .hint_pd_prefix.iapdprefix.lifetime_valid = (be32_t) -1, + .req_opts = TAKE_PTR(req_opts), + }; + + *ret = TAKE_PTR(client); + + return 0; +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c new file mode 100644 index 00000000..e37acfe8 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c @@ -0,0 +1,428 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014-2015 Intel Corporation. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include <errno.h> + +#include "alloc-util.h" +#include "dhcp6-lease-internal.h" +#include "dhcp6-protocol.h" +#include "strv.h" +#include "util.h" + +int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire) { + DHCP6Address *addr; + uint32_t valid = 0, t; + + assert_return(ia, -EINVAL); + assert_return(expire, -EINVAL); + + LIST_FOREACH(addresses, addr, ia->addresses) { + t = be32toh(addr->iaaddr.lifetime_valid); + if (valid < t) + valid = t; + } + + t = be32toh(ia->ia_na.lifetime_t2); + if (t > valid) + return -EINVAL; + + *expire = valid - t; + + return 0; +} + +DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia) { + DHCP6Address *address; + + if (!ia) + return NULL; + + while (ia->addresses) { + address = ia->addresses; + + LIST_REMOVE(addresses, ia->addresses, address); + + free(address); + } + + return NULL; +} + +int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, + size_t len) { + uint8_t *serverid; + + assert_return(lease, -EINVAL); + assert_return(id, -EINVAL); + + serverid = memdup(id, len); + if (!serverid) + return -ENOMEM; + + free_and_replace(lease->serverid, serverid); + lease->serverid_len = len; + + return 0; +} + +int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **id, size_t *len) { + assert_return(lease, -EINVAL); + + if (!lease->serverid) + return -ENOMSG; + + if (id) + *id = lease->serverid; + if (len) + *len = lease->serverid_len; + + return 0; +} + +int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference) { + assert_return(lease, -EINVAL); + + lease->preference = preference; + + return 0; +} + +int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference) { + assert_return(preference, -EINVAL); + + if (!lease) + return -EINVAL; + + *preference = lease->preference; + + return 0; +} + +int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease) { + assert_return(lease, -EINVAL); + + lease->rapid_commit = true; + + return 0; +} + +int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit) { + assert_return(lease, -EINVAL); + assert_return(rapid_commit, -EINVAL); + + *rapid_commit = lease->rapid_commit; + + return 0; +} + +int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid) { + assert_return(lease, -EINVAL); + assert_return(iaid, -EINVAL); + + *iaid = lease->ia.ia_na.id; + + return 0; +} + +int dhcp6_lease_get_pd_iaid(sd_dhcp6_lease *lease, be32_t *iaid) { + assert_return(lease, -EINVAL); + assert_return(iaid, -EINVAL); + + *iaid = lease->pd.ia_pd.id; + + return 0; +} + +int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, struct in6_addr *addr, + uint32_t *lifetime_preferred, + uint32_t *lifetime_valid) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(lifetime_preferred, -EINVAL); + assert_return(lifetime_valid, -EINVAL); + + if (!lease->addr_iter) + return -ENOMSG; + + memcpy(addr, &lease->addr_iter->iaaddr.address, + sizeof(struct in6_addr)); + *lifetime_preferred = + be32toh(lease->addr_iter->iaaddr.lifetime_preferred); + *lifetime_valid = be32toh(lease->addr_iter->iaaddr.lifetime_valid); + + lease->addr_iter = lease->addr_iter->addresses_next; + + return 0; +} + +void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) { + if (lease) + lease->addr_iter = lease->ia.addresses; +} + +int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix, + uint8_t *prefix_len, + uint32_t *lifetime_preferred, + uint32_t *lifetime_valid) { + assert_return(lease, -EINVAL); + assert_return(prefix, -EINVAL); + assert_return(prefix_len, -EINVAL); + assert_return(lifetime_preferred, -EINVAL); + assert_return(lifetime_valid, -EINVAL); + + if (!lease->prefix_iter) + return -ENOMSG; + + memcpy(prefix, &lease->prefix_iter->iapdprefix.address, + sizeof(struct in6_addr)); + *prefix_len = lease->prefix_iter->iapdprefix.prefixlen; + *lifetime_preferred = + be32toh(lease->prefix_iter->iapdprefix.lifetime_preferred); + *lifetime_valid = + be32toh(lease->prefix_iter->iapdprefix.lifetime_valid); + + lease->prefix_iter = lease->prefix_iter->addresses_next; + + return 0; +} + +void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease) { + if (lease) + lease->prefix_iter = lease->pd.addresses; +} + +int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { + int r; + + assert_return(lease, -EINVAL); + assert_return(optval, -EINVAL); + + if (!optlen) + return 0; + + r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->dns, + lease->dns_count); + if (r < 0) + return r; + + lease->dns_count = r; + + return 0; +} + +int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **addrs) { + assert_return(lease, -EINVAL); + assert_return(addrs, -EINVAL); + + if (lease->dns_count) { + *addrs = lease->dns; + return lease->dns_count; + } + + return -ENOENT; +} + +int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval, + size_t optlen) { + int r; + char **domains; + + assert_return(lease, -EINVAL); + assert_return(optval, -EINVAL); + + if (!optlen) + return 0; + + r = dhcp6_option_parse_domainname_list(optval, optlen, &domains); + if (r < 0) + return 0; + + strv_free_and_replace(lease->domains, domains); + lease->domains_count = r; + + return r; +} + +int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains) { + assert_return(lease, -EINVAL); + assert_return(domains, -EINVAL); + + if (lease->domains_count) { + *domains = lease->domains; + return lease->domains_count; + } + + return -ENOENT; +} + +int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { + int r; + uint16_t subopt; + size_t sublen; + uint8_t *subval; + + assert_return(lease, -EINVAL); + assert_return(optval, -EINVAL); + + lease->ntp = mfree(lease->ntp); + lease->ntp_count = 0; + + while ((r = dhcp6_option_parse(&optval, &optlen, &subopt, &sublen, + &subval)) >= 0) { + int s; + char **servers; + + switch(subopt) { + case DHCP6_NTP_SUBOPTION_SRV_ADDR: + case DHCP6_NTP_SUBOPTION_MC_ADDR: + if (sublen != 16) + return 0; + + s = dhcp6_option_parse_ip6addrs(subval, sublen, + &lease->ntp, + lease->ntp_count); + if (s < 0) + return s; + + lease->ntp_count = s; + + break; + + case DHCP6_NTP_SUBOPTION_SRV_FQDN: + r = dhcp6_option_parse_domainname_list(subval, sublen, + &servers); + if (r < 0) + return 0; + + strv_free_and_replace(lease->ntp_fqdn, servers); + lease->ntp_fqdn_count = r; + + break; + } + } + + if (r != -ENOMSG) + return r; + + return 0; +} + +int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { + int r; + + assert_return(lease, -EINVAL); + assert_return(optval, -EINVAL); + + if (!optlen) + return 0; + + if (lease->ntp || lease->ntp_fqdn) + return -EEXIST; + + /* Using deprecated SNTP information */ + + r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->ntp, + lease->ntp_count); + if (r < 0) + return r; + + lease->ntp_count = r; + + return 0; +} + +int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, + const struct in6_addr **addrs) { + assert_return(lease, -EINVAL); + assert_return(addrs, -EINVAL); + + if (lease->ntp_count) { + *addrs = lease->ntp; + return lease->ntp_count; + } + + return -ENOENT; +} + +int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn) { + assert_return(lease, -EINVAL); + assert_return(ntp_fqdn, -EINVAL); + + if (lease->ntp_fqdn_count) { + *ntp_fqdn = lease->ntp_fqdn; + return lease->ntp_fqdn_count; + } + + return -ENOENT; +} + +int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, + size_t optlen) { + int r; + char *fqdn; + + assert_return(lease, -EINVAL); + assert_return(optval, -EINVAL); + + if (optlen < 2) + return -ENODATA; + + /* Ignore the flags field, it doesn't carry any useful + information for clients. */ + r = dhcp6_option_parse_domainname(optval + 1, optlen - 1, &fqdn); + if (r < 0) + return r; + + return free_and_replace(lease->fqdn, fqdn); +} + +int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **fqdn) { + assert_return(lease, -EINVAL); + assert_return(fqdn, -EINVAL); + + if (lease->fqdn) { + *fqdn = lease->fqdn; + return 0; + } + + return -ENOENT; +} + +static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) { + assert(lease); + + free(lease->serverid); + dhcp6_lease_free_ia(&lease->ia); + dhcp6_lease_free_ia(&lease->pd); + + free(lease->dns); + free(lease->fqdn); + + lease->domains = strv_free(lease->domains); + + free(lease->ntp); + + lease->ntp_fqdn = strv_free(lease->ntp_fqdn); + return mfree(lease); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_lease, sd_dhcp6_lease, dhcp6_lease_free); + +int dhcp6_lease_new(sd_dhcp6_lease **ret) { + sd_dhcp6_lease *lease; + + lease = new0(sd_dhcp6_lease, 1); + if (!lease) + return -ENOMEM; + + lease->n_ref = 1; + + LIST_HEAD_INIT(lease->ia.addresses); + + *ret = lease; + return 0; +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4acd.c b/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4acd.c new file mode 100644 index 00000000..031804ca --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4acd.c @@ -0,0 +1,553 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014 Axis Communications AB. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include <arpa/inet.h> +#include <errno.h> +#include <netinet/if_ether.h> +#include <stdio.h> +#include <stdlib.h> + +#include "sd-ipv4acd.h" + +#include "alloc-util.h" +#include "arp-util.h" +#include "ether-addr-util.h" +#include "event-util.h" +#include "fd-util.h" +#include "in-addr-util.h" +#include "log-link.h" +#include "network-common.h" +#include "random-util.h" +#include "siphash24.h" +#include "string-table.h" +#include "string-util.h" +#include "time-util.h" + +/* Constants from the RFC */ +#define PROBE_WAIT_USEC (1U * USEC_PER_SEC) +#define PROBE_NUM 3U +#define PROBE_MIN_USEC (1U * USEC_PER_SEC) +#define PROBE_MAX_USEC (2U * USEC_PER_SEC) +#define ANNOUNCE_WAIT_USEC (2U * USEC_PER_SEC) +#define ANNOUNCE_NUM 2U +#define ANNOUNCE_INTERVAL_USEC (2U * USEC_PER_SEC) +#define MAX_CONFLICTS 10U +#define RATE_LIMIT_INTERVAL_USEC (60U * USEC_PER_SEC) +#define DEFEND_INTERVAL_USEC (10U * USEC_PER_SEC) + +typedef enum IPv4ACDState { + IPV4ACD_STATE_INIT, + IPV4ACD_STATE_STARTED, + IPV4ACD_STATE_WAITING_PROBE, + IPV4ACD_STATE_PROBING, + IPV4ACD_STATE_WAITING_ANNOUNCE, + IPV4ACD_STATE_ANNOUNCING, + IPV4ACD_STATE_RUNNING, + _IPV4ACD_STATE_MAX, + _IPV4ACD_STATE_INVALID = -EINVAL, +} IPv4ACDState; + +struct sd_ipv4acd { + unsigned n_ref; + + IPv4ACDState state; + int ifindex; + int fd; + + char *ifname; + unsigned n_iteration; + unsigned n_conflict; + + sd_event_source *receive_message_event_source; + sd_event_source *timer_event_source; + + usec_t defend_window; + be32_t address; + + /* External */ + struct ether_addr mac_addr; + + sd_event *event; + int event_priority; + sd_ipv4acd_callback_t callback; + void* userdata; +}; + +#define log_ipv4acd_errno(acd, error, fmt, ...) \ + log_interface_prefix_full_errno( \ + "IPv4ACD: ", \ + sd_ipv4acd_get_ifname(acd), \ + error, fmt, ##__VA_ARGS__) +#define log_ipv4acd(acd, fmt, ...) \ + log_interface_prefix_full_errno_zerook( \ + "IPv4ACD: ", \ + sd_ipv4acd_get_ifname(acd), \ + 0, fmt, ##__VA_ARGS__) + +static const char * const ipv4acd_state_table[_IPV4ACD_STATE_MAX] = { + [IPV4ACD_STATE_INIT] = "init", + [IPV4ACD_STATE_STARTED] = "started", + [IPV4ACD_STATE_WAITING_PROBE] = "waiting-probe", + [IPV4ACD_STATE_PROBING] = "probing", + [IPV4ACD_STATE_WAITING_ANNOUNCE] = "waiting-announce", + [IPV4ACD_STATE_ANNOUNCING] = "announcing", + [IPV4ACD_STATE_RUNNING] = "running", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(ipv4acd_state, IPv4ACDState); + +static void ipv4acd_set_state(sd_ipv4acd *acd, IPv4ACDState st, bool reset_counter) { + assert(acd); + assert(st < _IPV4ACD_STATE_MAX); + + if (st != acd->state) + log_ipv4acd(acd, "%s -> %s", ipv4acd_state_to_string(acd->state), ipv4acd_state_to_string(st)); + + if (st == acd->state && !reset_counter) + acd->n_iteration++; + else { + acd->state = st; + acd->n_iteration = 0; + } +} + +static void ipv4acd_reset(sd_ipv4acd *acd) { + assert(acd); + + (void) event_source_disable(acd->timer_event_source); + acd->receive_message_event_source = sd_event_source_unref(acd->receive_message_event_source); + + acd->fd = safe_close(acd->fd); + + ipv4acd_set_state(acd, IPV4ACD_STATE_INIT, true); +} + +static sd_ipv4acd *ipv4acd_free(sd_ipv4acd *acd) { + assert(acd); + + acd->timer_event_source = sd_event_source_unref(acd->timer_event_source); + + ipv4acd_reset(acd); + sd_ipv4acd_detach_event(acd); + free(acd->ifname); + return mfree(acd); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_ipv4acd, sd_ipv4acd, ipv4acd_free); + +int sd_ipv4acd_new(sd_ipv4acd **ret) { + _cleanup_(sd_ipv4acd_unrefp) sd_ipv4acd *acd = NULL; + + assert_return(ret, -EINVAL); + + acd = new(sd_ipv4acd, 1); + if (!acd) + return -ENOMEM; + + *acd = (sd_ipv4acd) { + .n_ref = 1, + .state = IPV4ACD_STATE_INIT, + .ifindex = -1, + .fd = -1, + }; + + *ret = TAKE_PTR(acd); + + return 0; +} + +static void ipv4acd_client_notify(sd_ipv4acd *acd, int event) { + assert(acd); + + if (!acd->callback) + return; + + acd->callback(acd, event, acd->userdata); +} + +int sd_ipv4acd_stop(sd_ipv4acd *acd) { + IPv4ACDState old_state; + + if (!acd) + return 0; + + old_state = acd->state; + + ipv4acd_reset(acd); + + if (old_state == IPV4ACD_STATE_INIT) + return 0; + + log_ipv4acd(acd, "STOPPED"); + + ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_STOP); + + return 0; +} + +static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata); + +static int ipv4acd_set_next_wakeup(sd_ipv4acd *acd, usec_t usec, usec_t random_usec) { + usec_t next_timeout, time_now; + + assert(acd); + + next_timeout = usec; + + if (random_usec > 0) + next_timeout += (usec_t) random_u64() % random_usec; + + assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &time_now) >= 0); + + return event_reset_time(acd->event, &acd->timer_event_source, + clock_boottime_or_monotonic(), + time_now + next_timeout, 0, + ipv4acd_on_timeout, acd, + acd->event_priority, "ipv4acd-timer", true); +} + +static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, struct ether_arp *arp) { + assert(acd); + assert(arp); + + /* see the BPF */ + if (memcmp(arp->arp_spa, &acd->address, sizeof(acd->address)) == 0) + return true; + + /* the TPA matched instead of the SPA, this is not a conflict */ + return false; +} + +static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + sd_ipv4acd *acd = userdata; + int r = 0; + + assert(acd); + + switch (acd->state) { + + case IPV4ACD_STATE_STARTED: + ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_PROBE, true); + + if (acd->n_conflict >= MAX_CONFLICTS) { + char ts[FORMAT_TIMESPAN_MAX]; + log_ipv4acd(acd, "Max conflicts reached, delaying by %s", format_timespan(ts, sizeof(ts), RATE_LIMIT_INTERVAL_USEC, 0)); + + r = ipv4acd_set_next_wakeup(acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT_USEC); + if (r < 0) + goto fail; + } else { + r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC); + if (r < 0) + goto fail; + } + + break; + + case IPV4ACD_STATE_WAITING_PROBE: + case IPV4ACD_STATE_PROBING: + /* Send a probe */ + r = arp_send_probe(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) { + log_ipv4acd_errno(acd, r, "Failed to send ARP probe: %m"); + goto fail; + } else { + _cleanup_free_ char *address = NULL; + union in_addr_union addr = { .in.s_addr = acd->address }; + + (void) in_addr_to_string(AF_INET, &addr, &address); + log_ipv4acd(acd, "Probing %s", strna(address)); + } + + if (acd->n_iteration < PROBE_NUM - 2) { + ipv4acd_set_state(acd, IPV4ACD_STATE_PROBING, false); + + r = ipv4acd_set_next_wakeup(acd, PROBE_MIN_USEC, (PROBE_MAX_USEC-PROBE_MIN_USEC)); + if (r < 0) + goto fail; + } else { + ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_ANNOUNCE, true); + + r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_WAIT_USEC, 0); + if (r < 0) + goto fail; + } + + break; + + case IPV4ACD_STATE_ANNOUNCING: + if (acd->n_iteration >= ANNOUNCE_NUM - 1) { + ipv4acd_set_state(acd, IPV4ACD_STATE_RUNNING, false); + break; + } + + _fallthrough_; + case IPV4ACD_STATE_WAITING_ANNOUNCE: + /* Send announcement packet */ + r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) { + log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m"); + goto fail; + } else + log_ipv4acd(acd, "ANNOUNCE"); + + ipv4acd_set_state(acd, IPV4ACD_STATE_ANNOUNCING, false); + + r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_INTERVAL_USEC, 0); + if (r < 0) + goto fail; + + if (acd->n_iteration == 0) { + acd->n_conflict = 0; + ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_BIND); + } + + break; + + default: + assert_not_reached("Invalid state."); + } + + return 0; + +fail: + sd_ipv4acd_stop(acd); + return 0; +} + +static void ipv4acd_on_conflict(sd_ipv4acd *acd) { + _cleanup_free_ char *address = NULL; + union in_addr_union addr = { .in.s_addr = acd->address }; + + assert(acd); + + acd->n_conflict++; + + (void) in_addr_to_string(AF_INET, &addr, &address); + log_ipv4acd(acd, "Conflict on %s (%u)", strna(address), acd->n_conflict); + + ipv4acd_reset(acd); + ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_CONFLICT); +} + +static int ipv4acd_on_packet( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata) { + + sd_ipv4acd *acd = userdata; + struct ether_arp packet; + ssize_t n; + int r; + + assert(s); + assert(acd); + assert(fd >= 0); + + n = recv(fd, &packet, sizeof(struct ether_arp), 0); + if (n < 0) { + if (IN_SET(errno, EAGAIN, EINTR)) + return 0; + + log_ipv4acd_errno(acd, errno, "Failed to read ARP packet: %m"); + goto fail; + } + if ((size_t) n != sizeof(struct ether_arp)) { + log_ipv4acd(acd, "Ignoring too short ARP packet."); + return 0; + } + + switch (acd->state) { + + case IPV4ACD_STATE_ANNOUNCING: + case IPV4ACD_STATE_RUNNING: + + if (ipv4acd_arp_conflict(acd, &packet)) { + usec_t ts; + + assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &ts) >= 0); + + /* Defend address */ + if (ts > acd->defend_window) { + acd->defend_window = ts + DEFEND_INTERVAL_USEC; + r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) { + log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m"); + goto fail; + } else + log_ipv4acd(acd, "DEFEND"); + + } else + ipv4acd_on_conflict(acd); + } + break; + + case IPV4ACD_STATE_WAITING_PROBE: + case IPV4ACD_STATE_PROBING: + case IPV4ACD_STATE_WAITING_ANNOUNCE: + /* BPF ensures this packet indicates a conflict */ + ipv4acd_on_conflict(acd); + break; + + default: + assert_not_reached("Invalid state."); + } + + return 0; + +fail: + sd_ipv4acd_stop(acd); + return 0; +} + +int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int ifindex) { + assert_return(acd, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + acd->ifindex = ifindex; + + return 0; +} + +int sd_ipv4acd_get_ifindex(sd_ipv4acd *acd) { + if (!acd) + return -EINVAL; + + return acd->ifindex; +} + +int sd_ipv4acd_set_ifname(sd_ipv4acd *acd, const char *ifname) { + assert_return(acd, -EINVAL); + assert_return(ifname, -EINVAL); + + if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE)) + return -EINVAL; + + return free_and_strdup(&acd->ifname, ifname); +} + +const char *sd_ipv4acd_get_ifname(sd_ipv4acd *acd) { + if (!acd) + return NULL; + + return get_ifname(acd->ifindex, &acd->ifname); +} + +int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr) { + assert_return(acd, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + acd->mac_addr = *addr; + + return 0; +} + +int sd_ipv4acd_detach_event(sd_ipv4acd *acd) { + assert_return(acd, -EINVAL); + + acd->event = sd_event_unref(acd->event); + + return 0; +} + +int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority) { + int r; + + assert_return(acd, -EINVAL); + assert_return(!acd->event, -EBUSY); + + if (event) + acd->event = sd_event_ref(event); + else { + r = sd_event_default(&acd->event); + if (r < 0) + return r; + } + + acd->event_priority = priority; + + return 0; +} + +int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata) { + assert_return(acd, -EINVAL); + + acd->callback = cb; + acd->userdata = userdata; + + return 0; +} + +int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address) { + assert_return(acd, -EINVAL); + assert_return(address, -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + acd->address = address->s_addr; + + return 0; +} + +int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address) { + assert_return(acd, -EINVAL); + assert_return(address, -EINVAL); + + address->s_addr = acd->address; + + return 0; +} + +int sd_ipv4acd_is_running(sd_ipv4acd *acd) { + assert_return(acd, false); + + return acd->state != IPV4ACD_STATE_INIT; +} + +int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts) { + int r; + + assert_return(acd, -EINVAL); + assert_return(acd->event, -EINVAL); + assert_return(acd->ifindex > 0, -EINVAL); + assert_return(acd->address != 0, -EINVAL); + assert_return(!ether_addr_is_null(&acd->mac_addr), -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + r = arp_network_bind_raw_socket(acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) + return r; + + CLOSE_AND_REPLACE(acd->fd, r); + acd->defend_window = 0; + + if (reset_conflicts) + acd->n_conflict = 0; + + r = sd_event_add_io(acd->event, &acd->receive_message_event_source, acd->fd, EPOLLIN, ipv4acd_on_packet, acd); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(acd->receive_message_event_source, acd->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(acd->receive_message_event_source, "ipv4acd-receive-message"); + + r = ipv4acd_set_next_wakeup(acd, 0, 0); + if (r < 0) + goto fail; + + ipv4acd_set_state(acd, IPV4ACD_STATE_STARTED, true); + return 0; + +fail: + ipv4acd_reset(acd); + return r; +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4ll.c b/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4ll.c new file mode 100644 index 00000000..56275100 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4ll.c @@ -0,0 +1,355 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014 Axis Communications AB. All rights reserved. +***/ + +#include "nm-sd-adapt-core.h" + +#include <arpa/inet.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include "sd-id128.h" +#include "sd-ipv4acd.h" +#include "sd-ipv4ll.h" + +#include "alloc-util.h" +#include "ether-addr-util.h" +#include "in-addr-util.h" +#include "log-link.h" +#include "random-util.h" +#include "siphash24.h" +#include "sparse-endian.h" +#include "string-util.h" +#include "util.h" + +#define IPV4LL_NETWORK UINT32_C(0xA9FE0000) +#define IPV4LL_NETMASK UINT32_C(0xFFFF0000) + +#define IPV4LL_DONT_DESTROY(ll) \ + _cleanup_(sd_ipv4ll_unrefp) _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll) + +struct sd_ipv4ll { + unsigned n_ref; + + sd_ipv4acd *acd; + + be32_t address; /* the address pushed to ACD */ + struct ether_addr mac; + + struct { + le64_t value; + le64_t generation; + } seed; + bool seed_set; + + /* External */ + be32_t claimed_address; + + sd_ipv4ll_callback_t callback; + void* userdata; +}; + +#define log_ipv4ll_errno(ll, error, fmt, ...) \ + log_interface_prefix_full_errno( \ + "IPv4LL: ", \ + sd_ipv4ll_get_ifname(ll), \ + error, fmt, ##__VA_ARGS__) +#define log_ipv4ll(ll, fmt, ...) \ + log_interface_prefix_full_errno_zerook( \ + "IPv4LL: ", \ + sd_ipv4ll_get_ifname(ll), \ + 0, fmt, ##__VA_ARGS__) + +static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata); + +static sd_ipv4ll *ipv4ll_free(sd_ipv4ll *ll) { + assert(ll); + + sd_ipv4acd_unref(ll->acd); + return mfree(ll); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_ipv4ll, sd_ipv4ll, ipv4ll_free); + +int sd_ipv4ll_new(sd_ipv4ll **ret) { + _cleanup_(sd_ipv4ll_unrefp) sd_ipv4ll *ll = NULL; + int r; + + assert_return(ret, -EINVAL); + + ll = new0(sd_ipv4ll, 1); + if (!ll) + return -ENOMEM; + + ll->n_ref = 1; + + r = sd_ipv4acd_new(&ll->acd); + if (r < 0) + return r; + + r = sd_ipv4acd_set_callback(ll->acd, ipv4ll_on_acd, ll); + if (r < 0) + return r; + + *ret = TAKE_PTR(ll); + + return 0; +} + +int sd_ipv4ll_stop(sd_ipv4ll *ll) { + if (!ll) + return 0; + + return sd_ipv4acd_stop(ll->acd); +} + +int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) { + assert_return(ll, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); + + return sd_ipv4acd_set_ifindex(ll->acd, ifindex); +} + +int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll) { + if (!ll) + return -EINVAL; + + return sd_ipv4acd_get_ifindex(ll->acd); +} + +int sd_ipv4ll_set_ifname(sd_ipv4ll *ll, const char *ifname) { + assert_return(ll, -EINVAL); + assert_return(ifname, -EINVAL); + + return sd_ipv4acd_set_ifname(ll->acd, ifname); +} + +const char *sd_ipv4ll_get_ifname(sd_ipv4ll *ll) { + if (!ll) + return NULL; + + return sd_ipv4acd_get_ifname(ll->acd); +} + +int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) { + int r; + + assert_return(ll, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); + + r = sd_ipv4acd_set_mac(ll->acd, addr); + if (r < 0) + return r; + + ll->mac = *addr; + return 0; +} + +int sd_ipv4ll_detach_event(sd_ipv4ll *ll) { + assert_return(ll, -EINVAL); + + return sd_ipv4acd_detach_event(ll->acd); +} + +int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority) { + assert_return(ll, -EINVAL); + + return sd_ipv4acd_attach_event(ll->acd, event, priority); +} + +int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata) { + assert_return(ll, -EINVAL); + + ll->callback = cb; + ll->userdata = userdata; + + return 0; +} + +int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address) { + assert_return(ll, -EINVAL); + assert_return(address, -EINVAL); + + if (ll->claimed_address == 0) + return -ENOENT; + + address->s_addr = ll->claimed_address; + + return 0; +} + +int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed) { + assert_return(ll, -EINVAL); + assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); + + ll->seed.value = htole64(seed); + ll->seed_set = true; + + return 0; +} + +int sd_ipv4ll_is_running(sd_ipv4ll *ll) { + assert_return(ll, false); + + return sd_ipv4acd_is_running(ll->acd); +} + +static bool ipv4ll_address_is_valid(const struct in_addr *address) { + assert(address); + + if (!in4_addr_is_link_local(address)) + return false; + + return !IN_SET(be32toh(address->s_addr) & 0x0000FF00U, 0x0000U, 0xFF00U); +} + +int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address) { + int r; + + assert_return(ll, -EINVAL); + assert_return(address, -EINVAL); + assert_return(ipv4ll_address_is_valid(address), -EINVAL); + + r = sd_ipv4acd_set_address(ll->acd, address); + if (r < 0) + return r; + + ll->address = address->s_addr; + + return 0; +} + +#define PICK_HASH_KEY SD_ID128_MAKE(15,ac,82,a6,d6,3f,49,78,98,77,5d,0c,69,02,94,0b) + +static int ipv4ll_pick_address(sd_ipv4ll *ll) { + _cleanup_free_ char *address = NULL; + be32_t addr; + + assert(ll); + + do { + uint64_t h; + + h = siphash24(&ll->seed, sizeof(ll->seed), PICK_HASH_KEY.bytes); + + /* Increase the generation counter by one */ + ll->seed.generation = htole64(le64toh(ll->seed.generation) + 1); + + addr = htobe32((h & UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK); + } while (addr == ll->address || + IN_SET(be32toh(addr) & 0x0000FF00U, 0x0000U, 0xFF00U)); + + (void) in_addr_to_string(AF_INET, &(union in_addr_union) { .in.s_addr = addr }, &address); + log_ipv4ll(ll, "Picked new IP address %s.", strna(address)); + + return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr }); +} + +#define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2) + +static int ipv4ll_start_internal(sd_ipv4ll *ll, bool reset_generation) { + int r; + bool picked_address = false; + + assert_return(ll, -EINVAL); + assert_return(!ether_addr_is_null(&ll->mac), -EINVAL); + + /* If no random seed is set, generate some from the MAC address */ + if (!ll->seed_set) + ll->seed.value = htole64(siphash24(ll->mac.ether_addr_octet, ETH_ALEN, MAC_HASH_KEY.bytes)); + + if (reset_generation) + ll->seed.generation = 0; + + if (ll->address == 0) { + r = ipv4ll_pick_address(ll); + if (r < 0) + return r; + + picked_address = true; + } + + r = sd_ipv4acd_start(ll->acd, reset_generation); + if (r < 0) { + + /* We couldn't start? If so, let's forget the picked address again, the user might make a change and + * retry, and we want the new data to take effect when picking an address. */ + if (picked_address) + ll->address = 0; + + return r; + } + + return 1; +} + +int sd_ipv4ll_start(sd_ipv4ll *ll) { + assert_return(ll, -EINVAL); + + if (sd_ipv4ll_is_running(ll)) + return 0; + + return ipv4ll_start_internal(ll, true); +} + +int sd_ipv4ll_restart(sd_ipv4ll *ll) { + ll->address = 0; + + return ipv4ll_start_internal(ll, false); +} + +static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) { + assert(ll); + + if (ll->callback) + ll->callback(ll, event, ll->userdata); +} + +void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) { + sd_ipv4ll *ll = userdata; + IPV4LL_DONT_DESTROY(ll); + int r; + + assert(acd); + assert(ll); + + switch (event) { + + case SD_IPV4ACD_EVENT_STOP: + ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP); + ll->claimed_address = 0; + break; + + case SD_IPV4ACD_EVENT_BIND: + ll->claimed_address = ll->address; + ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_BIND); + break; + + case SD_IPV4ACD_EVENT_CONFLICT: + /* if an address was already bound we must call up to the + user to handle this, otherwise we just try again */ + if (ll->claimed_address != 0) { + ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_CONFLICT); + + ll->claimed_address = 0; + } else { + r = sd_ipv4ll_restart(ll); + if (r < 0) + goto error; + } + + break; + + default: + assert_not_reached("Invalid IPv4ACD event."); + } + + return; + +error: + ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP); +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-lldp.c b/src/libnm-systemd-core/src/libsystemd-network/sd-lldp.c new file mode 100644 index 00000000..09c2052f --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd-network/sd-lldp.c @@ -0,0 +1,524 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include <arpa/inet.h> +#include <linux/sockios.h> +#include <sys/ioctl.h> + +#include "sd-lldp.h" + +#include "alloc-util.h" +#include "ether-addr-util.h" +#include "event-util.h" +#include "fd-util.h" +#include "lldp-internal.h" +#include "lldp-neighbor.h" +#include "lldp-network.h" +#include "memory-util.h" +#include "network-common.h" +#include "socket-util.h" +#include "sort-util.h" +#include "string-table.h" + +#define LLDP_DEFAULT_NEIGHBORS_MAX 128U + +static const char * const lldp_event_table[_SD_LLDP_EVENT_MAX] = { + [SD_LLDP_EVENT_ADDED] = "added", + [SD_LLDP_EVENT_REMOVED] = "removed", + [SD_LLDP_EVENT_UPDATED] = "updated", + [SD_LLDP_EVENT_REFRESHED] = "refreshed", +}; + +DEFINE_STRING_TABLE_LOOKUP(lldp_event, sd_lldp_event_t); + +static void lldp_flush_neighbors(sd_lldp *lldp) { + assert(lldp); + + hashmap_clear(lldp->neighbor_by_id); +} + +static void lldp_callback(sd_lldp *lldp, sd_lldp_event_t event, sd_lldp_neighbor *n) { + assert(lldp); + assert(event >= 0 && event < _SD_LLDP_EVENT_MAX); + + if (!lldp->callback) + return (void) log_lldp(lldp, "Received '%s' event.", lldp_event_to_string(event)); + + log_lldp(lldp, "Invoking callback for '%s' event.", lldp_event_to_string(event)); + lldp->callback(lldp, event, n, lldp->userdata); +} + +static int lldp_make_space(sd_lldp *lldp, size_t extra) { + usec_t t = USEC_INFINITY; + bool changed = false; + + assert(lldp); + + /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries + * are free. */ + + for (;;) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + + n = prioq_peek(lldp->neighbor_by_expiry); + if (!n) + break; + + sd_lldp_neighbor_ref(n); + + if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra)) + goto remove_one; + + if (t == USEC_INFINITY) + t = now(clock_boottime_or_monotonic()); + + if (n->until > t) + break; + + remove_one: + lldp_neighbor_unlink(n); + lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n); + changed = true; + } + + return changed; +} + +static bool lldp_keep_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) { + assert(lldp); + assert(n); + + /* Don't keep data with a zero TTL */ + if (n->ttl <= 0) + return false; + + /* Filter out data from the filter address */ + if (!ether_addr_is_null(&lldp->filter_address) && + ether_addr_equal(&lldp->filter_address, &n->source_address)) + return false; + + /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with + * no caps field set. */ + if (n->has_capabilities && + (n->enabled_capabilities & lldp->capability_mask) == 0) + return false; + + /* Keep everything else */ + return true; +} + +static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor); + +static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL; + bool keep; + int r; + + assert(lldp); + assert(n); + assert(!n->lldp); + + keep = lldp_keep_neighbor(lldp, n); + + /* First retrieve the old entry for this MSAP */ + old = hashmap_get(lldp->neighbor_by_id, &n->id); + if (old) { + sd_lldp_neighbor_ref(old); + + if (!keep) { + lldp_neighbor_unlink(old); + lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old); + return 0; + } + + if (lldp_neighbor_equal(n, old)) { + /* Is this equal, then restart the TTL counter, but don't do anything else. */ + old->timestamp = n->timestamp; + lldp_start_timer(lldp, old); + lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old); + return 0; + } + + /* Data changed, remove the old entry, and add a new one */ + lldp_neighbor_unlink(old); + + } else if (!keep) + return 0; + + /* Then, make room for at least one new neighbor */ + lldp_make_space(lldp, 1); + + r = hashmap_put(lldp->neighbor_by_id, &n->id, n); + if (r < 0) + goto finish; + + r = prioq_put(lldp->neighbor_by_expiry, n, &n->prioq_idx); + if (r < 0) { + assert_se(hashmap_remove(lldp->neighbor_by_id, &n->id) == n); + goto finish; + } + + n->lldp = lldp; + + lldp_start_timer(lldp, n); + lldp_callback(lldp, old ? SD_LLDP_EVENT_UPDATED : SD_LLDP_EVENT_ADDED, n); + + return 1; + +finish: + if (old) + lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old); + + return r; +} + +static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) { + int r; + + assert(lldp); + assert(n); + + r = lldp_neighbor_parse(n); + if (r == -EBADMSG) /* Ignore bad messages */ + return 0; + if (r < 0) + return r; + + r = lldp_add_neighbor(lldp, n); + if (r < 0) { + log_lldp_errno(lldp, r, "Failed to add datagram. Ignoring."); + return 0; + } + + log_lldp(lldp, "Successfully processed LLDP datagram."); + return 0; +} + +static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + ssize_t space, length; + sd_lldp *lldp = userdata; + struct timespec ts; + + assert(fd >= 0); + assert(lldp); + + space = next_datagram_size_fd(fd); + if (space < 0) { + log_lldp_errno(lldp, space, "Failed to determine datagram size to read, ignoring: %m"); + return 0; + } + + n = lldp_neighbor_new(space); + if (!n) + return -ENOMEM; + + length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT); + if (length < 0) { + if (IN_SET(errno, EAGAIN, EINTR)) + return 0; + + log_lldp_errno(lldp, errno, "Failed to read LLDP datagram, ignoring: %m"); + return 0; + } + + if ((size_t) length != n->raw_size) { + log_lldp(lldp, "Packet size mismatch, ignoring"); + return 0; + } + + /* Try to get the timestamp of this packet if it is known */ + if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0) + triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts)); + else + triple_timestamp_get(&n->timestamp); + + return lldp_handle_datagram(lldp, n); +} + +static void lldp_reset(sd_lldp *lldp) { + assert(lldp); + + (void) event_source_disable(lldp->timer_event_source); + lldp->io_event_source = sd_event_source_unref(lldp->io_event_source); + lldp->fd = safe_close(lldp->fd); +} + +_public_ int sd_lldp_start(sd_lldp *lldp) { + int r; + + assert_return(lldp, -EINVAL); + assert_return(lldp->event, -EINVAL); + assert_return(lldp->ifindex > 0, -EINVAL); + + if (lldp->fd >= 0) + return 0; + + assert(!lldp->io_event_source); + + lldp->fd = lldp_network_bind_raw_socket(lldp->ifindex); + if (lldp->fd < 0) + return lldp->fd; + + r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io"); + + log_lldp(lldp, "Started LLDP client"); + return 1; + +fail: + lldp_reset(lldp); + return r; +} + +_public_ int sd_lldp_stop(sd_lldp *lldp) { + if (!lldp) + return 0; + + if (lldp->fd < 0) + return 0; + + log_lldp(lldp, "Stopping LLDP client"); + + lldp_reset(lldp); + lldp_flush_neighbors(lldp); + + return 1; +} + +_public_ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) { + int r; + + assert_return(lldp, -EINVAL); + assert_return(lldp->fd < 0, -EBUSY); + assert_return(!lldp->event, -EBUSY); + + if (event) + lldp->event = sd_event_ref(event); + else { + r = sd_event_default(&lldp->event); + if (r < 0) + return r; + } + + lldp->event_priority = priority; + + return 0; +} + +_public_ int sd_lldp_detach_event(sd_lldp *lldp) { + + assert_return(lldp, -EINVAL); + assert_return(lldp->fd < 0, -EBUSY); + + lldp->event = sd_event_unref(lldp->event); + return 0; +} + +_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) { + assert_return(lldp, NULL); + + return lldp->event; +} + +_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) { + assert_return(lldp, -EINVAL); + + lldp->callback = cb; + lldp->userdata = userdata; + + return 0; +} + +_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) { + assert_return(lldp, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(lldp->fd < 0, -EBUSY); + + lldp->ifindex = ifindex; + return 0; +} + +int sd_lldp_set_ifname(sd_lldp *lldp, const char *ifname) { + assert_return(lldp, -EINVAL); + assert_return(ifname, -EINVAL); + + if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE)) + return -EINVAL; + + return free_and_strdup(&lldp->ifname, ifname); +} + +const char *sd_lldp_get_ifname(sd_lldp *lldp) { + if (!lldp) + return NULL; + + return get_ifname(lldp->ifindex, &lldp->ifname); +} + +static sd_lldp* lldp_free(sd_lldp *lldp) { + assert(lldp); + + lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source); + + lldp_reset(lldp); + sd_lldp_detach_event(lldp); + lldp_flush_neighbors(lldp); + + hashmap_free(lldp->neighbor_by_id); + prioq_free(lldp->neighbor_by_expiry); + free(lldp->ifname); + return mfree(lldp); +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp, sd_lldp, lldp_free); + +_public_ int sd_lldp_new(sd_lldp **ret) { + _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL; + int r; + + assert_return(ret, -EINVAL); + + lldp = new(sd_lldp, 1); + if (!lldp) + return -ENOMEM; + + *lldp = (sd_lldp) { + .n_ref = 1, + .fd = -1, + .neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX, + .capability_mask = UINT16_MAX, + }; + + lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_hash_ops); + if (!lldp->neighbor_by_id) + return -ENOMEM; + + r = prioq_ensure_allocated(&lldp->neighbor_by_expiry, lldp_neighbor_prioq_compare_func); + if (r < 0) + return r; + + *ret = TAKE_PTR(lldp); + + return 0; +} + +static int neighbor_compare_func(sd_lldp_neighbor * const *a, sd_lldp_neighbor * const *b) { + return lldp_neighbor_id_compare_func(&(*a)->id, &(*b)->id); +} + +static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) { + sd_lldp *lldp = userdata; + int r; + + r = lldp_make_space(lldp, 0); + if (r < 0) { + log_lldp_errno(lldp, r, "Failed to make space, ignoring: %m"); + return 0; + } + + r = lldp_start_timer(lldp, NULL); + if (r < 0) { + log_lldp_errno(lldp, r, "Failed to restart timer, ignoring: %m"); + return 0; + } + + return 0; +} + +static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) { + sd_lldp_neighbor *n; + + assert(lldp); + + if (neighbor) + lldp_neighbor_start_ttl(neighbor); + + n = prioq_peek(lldp->neighbor_by_expiry); + if (!n) + return event_source_disable(lldp->timer_event_source); + + if (!lldp->event) + return 0; + + return event_reset_time(lldp->event, &lldp->timer_event_source, + clock_boottime_or_monotonic(), + n->until, 0, + on_timer_event, lldp, + lldp->event_priority, "lldp-timer", true); +} + +_public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) { + sd_lldp_neighbor **l = NULL, *n; + int k = 0, r; + + assert_return(lldp, -EINVAL); + assert_return(ret, -EINVAL); + + if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */ + *ret = NULL; + return 0; + } + + l = new0(sd_lldp_neighbor*, hashmap_size(lldp->neighbor_by_id)); + if (!l) + return -ENOMEM; + + r = lldp_start_timer(lldp, NULL); + if (r < 0) { + free(l); + return r; + } + + HASHMAP_FOREACH(n, lldp->neighbor_by_id) + l[k++] = sd_lldp_neighbor_ref(n); + + assert((size_t) k == hashmap_size(lldp->neighbor_by_id)); + + /* Return things in a stable order */ + typesafe_qsort(l, k, neighbor_compare_func); + *ret = l; + + return k; +} + +_public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) { + assert_return(lldp, -EINVAL); + assert_return(m > 0, -EINVAL); + + lldp->neighbors_max = m; + lldp_make_space(lldp, 0); + + return 0; +} + +_public_ int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask) { + assert_return(lldp, -EINVAL); + assert_return(mask != 0, -EINVAL); + + lldp->capability_mask = mask; + + return 0; +} + +_public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *addr) { + assert_return(lldp, -EINVAL); + + /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so + * that our own can be filtered out here. */ + + if (addr) + lldp->filter_address = *addr; + else + zero(lldp->filter_address); + + return 0; +} diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h b/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h new file mode 100644 index 00000000..d2dc2147 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h @@ -0,0 +1,220 @@ +#pragma once +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/epoll.h> +#include <sys/timerfd.h> +#include <sys/wait.h> + +#include "sd-event.h" + +#include "fs-util.h" +#include "hashmap.h" +#include "list.h" +#include "prioq.h" +#include "ratelimit.h" + +typedef enum EventSourceType { + SOURCE_IO, + SOURCE_TIME_REALTIME, + SOURCE_TIME_BOOTTIME, + SOURCE_TIME_MONOTONIC, + SOURCE_TIME_REALTIME_ALARM, + SOURCE_TIME_BOOTTIME_ALARM, + SOURCE_SIGNAL, + SOURCE_CHILD, + SOURCE_DEFER, + SOURCE_POST, + SOURCE_EXIT, + SOURCE_WATCHDOG, + SOURCE_INOTIFY, + _SOURCE_EVENT_SOURCE_TYPE_MAX, + _SOURCE_EVENT_SOURCE_TYPE_INVALID = -EINVAL, +} EventSourceType; + +/* All objects we use in epoll events start with this value, so that + * we know how to dispatch it */ +typedef enum WakeupType { + WAKEUP_NONE, + WAKEUP_EVENT_SOURCE, /* either I/O or pidfd wakeup */ + WAKEUP_CLOCK_DATA, + WAKEUP_SIGNAL_DATA, + WAKEUP_INOTIFY_DATA, + _WAKEUP_TYPE_MAX, + _WAKEUP_TYPE_INVALID = -EINVAL, +} WakeupType; + +struct inode_data; + +struct sd_event_source { + WakeupType wakeup; + + unsigned n_ref; + + sd_event *event; + void *userdata; + sd_event_handler_t prepare; + + char *description; + + EventSourceType type; + signed int enabled:3; + bool pending:1; + bool dispatching:1; + bool floating:1; + bool exit_on_failure:1; + bool ratelimited:1; + + int64_t priority; + unsigned pending_index; + unsigned prepare_index; + uint64_t pending_iteration; + uint64_t prepare_iteration; + + sd_event_destroy_t destroy_callback; + + LIST_FIELDS(sd_event_source, sources); + + RateLimit rate_limit; + + /* These are primarily fields relevant for time event sources, but since any event source can + * effectively become one when rate-limited, this is part of the common fields. */ + unsigned earliest_index; + unsigned latest_index; + + union { + struct { + sd_event_io_handler_t callback; + int fd; + uint32_t events; + uint32_t revents; + bool registered:1; + bool owned:1; + } io; + struct { + sd_event_time_handler_t callback; + usec_t next, accuracy; + } time; + struct { + sd_event_signal_handler_t callback; + struct signalfd_siginfo siginfo; + int sig; + } signal; + struct { + sd_event_child_handler_t callback; + siginfo_t siginfo; + pid_t pid; + int options; + int pidfd; + bool registered:1; /* whether the pidfd is registered in the epoll */ + bool pidfd_owned:1; /* close pidfd when event source is freed */ + bool process_owned:1; /* kill+reap process when event source is freed */ + bool exited:1; /* true if process exited (i.e. if there's value in SIGKILLing it if we want to get rid of it) */ + bool waited:1; /* true if process was waited for (i.e. if there's value in waitid(P_PID)'ing it if we want to get rid of it) */ + } child; + struct { + sd_event_handler_t callback; + } defer; + struct { + sd_event_handler_t callback; + } post; + struct { + sd_event_handler_t callback; + unsigned prioq_index; + } exit; + struct { + sd_event_inotify_handler_t callback; + uint32_t mask; + struct inode_data *inode_data; + LIST_FIELDS(sd_event_source, by_inode_data); + } inotify; + }; +}; + +struct clock_data { + WakeupType wakeup; + int fd; + + /* For all clocks we maintain two priority queues each, one + * ordered for the earliest times the events may be + * dispatched, and one ordered by the latest times they must + * have been dispatched. The range between the top entries in + * the two prioqs is the time window we can freely schedule + * wakeups in */ + + Prioq *earliest; + Prioq *latest; + usec_t next; + + bool needs_rearm:1; +}; + +struct signal_data { + WakeupType wakeup; + + /* For each priority we maintain one signal fd, so that we + * only have to dequeue a single event per priority at a + * time. */ + + int fd; + int64_t priority; + sigset_t sigset; + sd_event_source *current; +}; + +/* A structure listing all event sources currently watching a specific inode */ +struct inode_data { + /* The identifier for the inode, the combination of the .st_dev + .st_ino fields of the file */ + ino_t ino; + dev_t dev; + + /* An fd of the inode to watch. The fd is kept open until the next iteration of the loop, so that we can + * rearrange the priority still until then, as we need the original inode to change the priority as we need to + * add a watch descriptor to the right inotify for the priority which we can only do if we have a handle to the + * original inode. We keep a list of all inode_data objects with an open fd in the to_close list (see below) of + * the sd-event object, so that it is efficient to close everything, before entering the next event loop + * iteration. */ + int fd; + + /* The inotify "watch descriptor" */ + int wd; + + /* The combination of the mask of all inotify watches on this inode we manage. This is also the mask that has + * most recently been set on the watch descriptor. */ + uint32_t combined_mask; + + /* All event sources subscribed to this inode */ + LIST_HEAD(sd_event_source, event_sources); + + /* The inotify object we watch this inode with */ + struct inotify_data *inotify_data; + + /* A linked list of all inode data objects with fds to close (see above) */ + LIST_FIELDS(struct inode_data, to_close); +}; + +/* A structure encapsulating an inotify fd */ +struct inotify_data { + WakeupType wakeup; + + /* For each priority we maintain one inotify fd, so that we only have to dequeue a single event per priority at + * a time */ + + int fd; + int64_t priority; + + Hashmap *inodes; /* The inode_data structures keyed by dev+ino */ + Hashmap *wd; /* The inode_data structures keyed by the watch descriptor for each */ + + /* The buffer we read inotify events into */ + union inotify_event_buffer buffer; + size_t buffer_filled; /* fill level of the buffer */ + + /* How many event sources are currently marked pending for this inotify. We won't read new events off the + * inotify fd as long as there are still pending events on the inotify (because we have no strategy of queuing + * the events locally if they can't be coalesced). */ + unsigned n_pending; + + /* A linked list of all inotify objects with data already read, that still need processing. We keep this list + * to make it efficient to figure out what inotify objects to process data on next. */ + LIST_FIELDS(struct inotify_data, buffered); +}; diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c b/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c new file mode 100644 index 00000000..7cc55be1 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include <errno.h> + +#include "event-source.h" +#include "event-util.h" +#include "log.h" +#include "string-util.h" + +int event_reset_time( + sd_event *e, + sd_event_source **s, + clockid_t clock, + uint64_t usec, + uint64_t accuracy, + sd_event_time_handler_t callback, + void *userdata, + int64_t priority, + const char *description, + bool force_reset) { + + bool created = false; + int enabled, r; + clockid_t c; + + assert(e); + assert(s); + + if (*s) { + if (!force_reset) { + r = sd_event_source_get_enabled(*s, &enabled); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to query whether event source \"%s\" is enabled or not: %m", + strna((*s)->description ?: description)); + + if (enabled != SD_EVENT_OFF) + return 0; + } + + r = sd_event_source_get_time_clock(*s, &c); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to get clock id of event source \"%s\": %m", strna((*s)->description ?: description)); + + if (c != clock) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "sd-event: Current clock id %i of event source \"%s\" is different from specified one %i.", + (int)c, + strna((*s)->description ? : description), + (int)clock); + + r = sd_event_source_set_time(*s, usec); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to set time for event source \"%s\": %m", strna((*s)->description ?: description)); + + r = sd_event_source_set_time_accuracy(*s, accuracy); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to set accuracy for event source \"%s\": %m", strna((*s)->description ?: description)); + + /* callback function is not updated, as we do not have sd_event_source_set_time_callback(). */ + + (void) sd_event_source_set_userdata(*s, userdata); + + r = sd_event_source_set_enabled(*s, SD_EVENT_ONESHOT); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to enable event source \"%s\": %m", strna((*s)->description ?: description)); + } else { + r = sd_event_add_time(e, s, clock, usec, accuracy, callback, userdata); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to create timer event \"%s\": %m", strna(description)); + + created = true; + } + + r = sd_event_source_set_priority(*s, priority); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to set priority for event source \"%s\": %m", strna((*s)->description ?: description)); + + if (description) { + r = sd_event_source_set_description(*s, description); + if (r < 0) + return log_debug_errno(r, "sd-event: Failed to set description for event source \"%s\": %m", description); + } + + return created; +} + +int event_source_disable(sd_event_source *s) { + if (!s) + return 0; + + return sd_event_source_set_enabled(s, SD_EVENT_OFF); +} + +int event_source_is_enabled(sd_event_source *s) { + if (!s) + return false; + + return sd_event_source_get_enabled(s, NULL); +} diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h b/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h new file mode 100644 index 00000000..c8f97bc8 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <stdbool.h> + +#include "sd-event.h" + +int event_reset_time(sd_event *e, sd_event_source **s, + clockid_t clock, uint64_t usec, uint64_t accuracy, + sd_event_time_handler_t callback, void *userdata, + int64_t priority, const char *description, bool force_reset); +int event_source_disable(sd_event_source *s); +int event_source_is_enabled(sd_event_source *s); diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c b/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c new file mode 100644 index 00000000..44926744 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c @@ -0,0 +1,4461 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include <sys/epoll.h> +#include <sys/timerfd.h> +#include <sys/wait.h> + +#include "sd-daemon.h" +#include "sd-event.h" +#include "sd-id128.h" + +#include "alloc-util.h" +#include "env-util.h" +#include "event-source.h" +#include "fd-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "list.h" +#include "macro.h" +#include "memory-util.h" +#include "missing_syscall.h" +#include "prioq.h" +#include "process-util.h" +#include "set.h" +#include "signal-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strxcpyx.h" +#include "time-util.h" + +#define DEFAULT_ACCURACY_USEC (250 * USEC_PER_MSEC) + +static bool EVENT_SOURCE_WATCH_PIDFD(sd_event_source *s) { + /* Returns true if this is a PID event source and can be implemented by watching EPOLLIN */ + return s && + s->type == SOURCE_CHILD && + s->child.pidfd >= 0 && + s->child.options == WEXITED; +} + +static bool event_source_is_online(sd_event_source *s) { + assert(s); + return s->enabled != SD_EVENT_OFF && !s->ratelimited; +} + +static bool event_source_is_offline(sd_event_source *s) { + assert(s); + return s->enabled == SD_EVENT_OFF || s->ratelimited; +} + +static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = { + [SOURCE_IO] = "io", + [SOURCE_TIME_REALTIME] = "realtime", + [SOURCE_TIME_BOOTTIME] = "bootime", + [SOURCE_TIME_MONOTONIC] = "monotonic", + [SOURCE_TIME_REALTIME_ALARM] = "realtime-alarm", + [SOURCE_TIME_BOOTTIME_ALARM] = "boottime-alarm", + [SOURCE_SIGNAL] = "signal", + [SOURCE_CHILD] = "child", + [SOURCE_DEFER] = "defer", + [SOURCE_POST] = "post", + [SOURCE_EXIT] = "exit", + [SOURCE_WATCHDOG] = "watchdog", + [SOURCE_INOTIFY] = "inotify", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); + +#define EVENT_SOURCE_IS_TIME(t) \ + IN_SET((t), \ + SOURCE_TIME_REALTIME, \ + SOURCE_TIME_BOOTTIME, \ + SOURCE_TIME_MONOTONIC, \ + SOURCE_TIME_REALTIME_ALARM, \ + SOURCE_TIME_BOOTTIME_ALARM) + +#define EVENT_SOURCE_CAN_RATE_LIMIT(t) \ + IN_SET((t), \ + SOURCE_IO, \ + SOURCE_TIME_REALTIME, \ + SOURCE_TIME_BOOTTIME, \ + SOURCE_TIME_MONOTONIC, \ + SOURCE_TIME_REALTIME_ALARM, \ + SOURCE_TIME_BOOTTIME_ALARM, \ + SOURCE_SIGNAL, \ + SOURCE_DEFER, \ + SOURCE_INOTIFY) + +struct sd_event { + unsigned n_ref; + + int epoll_fd; + int watchdog_fd; + + Prioq *pending; + Prioq *prepare; + + /* timerfd_create() only supports these five clocks so far. We + * can add support for more clocks when the kernel learns to + * deal with them, too. */ + struct clock_data realtime; + struct clock_data boottime; + struct clock_data monotonic; + struct clock_data realtime_alarm; + struct clock_data boottime_alarm; + + usec_t perturb; + + sd_event_source **signal_sources; /* indexed by signal number */ + Hashmap *signal_data; /* indexed by priority */ + + Hashmap *child_sources; + unsigned n_online_child_sources; + + Set *post_sources; + + Prioq *exit; + + Hashmap *inotify_data; /* indexed by priority */ + + /* A list of inode structures that still have an fd open, that we need to close before the next loop iteration */ + LIST_HEAD(struct inode_data, inode_data_to_close); + + /* A list of inotify objects that already have events buffered which aren't processed yet */ + LIST_HEAD(struct inotify_data, inotify_data_buffered); + + pid_t original_pid; + + uint64_t iteration; + triple_timestamp timestamp; + int state; + + bool exit_requested:1; + bool need_process_child:1; + bool watchdog:1; + bool profile_delays:1; + + int exit_code; + + pid_t tid; + sd_event **default_event_ptr; + + usec_t watchdog_last, watchdog_period; + + unsigned n_sources; + + struct epoll_event *event_queue; + + LIST_HEAD(sd_event_source, sources); + + usec_t last_run_usec, last_log_usec; + unsigned delays[sizeof(usec_t) * 8]; +}; + +static thread_local sd_event *default_event = NULL; + +static void source_disconnect(sd_event_source *s); +static void event_gc_inode_data(sd_event *e, struct inode_data *d); + +static sd_event *event_resolve(sd_event *e) { + return e == SD_EVENT_DEFAULT ? default_event : e; +} + +static int pending_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + int r; + + assert(x->pending); + assert(y->pending); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Non rate-limited ones first. */ + r = CMP(!!x->ratelimited, !!y->ratelimited); + if (r != 0) + return r; + + /* Lower priority values first */ + r = CMP(x->priority, y->priority); + if (r != 0) + return r; + + /* Older entries first */ + return CMP(x->pending_iteration, y->pending_iteration); +} + +static int prepare_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + int r; + + assert(x->prepare); + assert(y->prepare); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Non rate-limited ones first. */ + r = CMP(!!x->ratelimited, !!y->ratelimited); + if (r != 0) + return r; + + /* Move most recently prepared ones last, so that we can stop + * preparing as soon as we hit one that has already been + * prepared in the current iteration */ + r = CMP(x->prepare_iteration, y->prepare_iteration); + if (r != 0) + return r; + + /* Lower priority values first */ + return CMP(x->priority, y->priority); +} + +static usec_t time_event_source_next(const sd_event_source *s) { + assert(s); + + /* We have two kinds of event sources that have elapsation times associated with them: the actual + * time based ones and the ones for which a ratelimit can be in effect (where we want to be notified + * once the ratelimit time window ends). Let's return the next elapsing time depending on what we are + * looking at here. */ + + if (s->ratelimited) { /* If rate-limited the next elapsation is when the ratelimit time window ends */ + assert(s->rate_limit.begin != 0); + assert(s->rate_limit.interval != 0); + return usec_add(s->rate_limit.begin, s->rate_limit.interval); + } + + /* Otherwise this must be a time event source, if not ratelimited */ + if (EVENT_SOURCE_IS_TIME(s->type)) + return s->time.next; + + return USEC_INFINITY; +} + +static int earliest_time_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Move the pending ones to the end */ + if (!x->pending && y->pending) + return -1; + if (x->pending && !y->pending) + return 1; + + /* Order by time */ + return CMP(time_event_source_next(x), time_event_source_next(y)); +} + +static usec_t time_event_source_latest(const sd_event_source *s) { + assert(s); + + if (s->ratelimited) { /* For ratelimited stuff the earliest and the latest time shall actually be the + * same, as we should avoid adding additional inaccuracy on an inaccuracy time + * window */ + assert(s->rate_limit.begin != 0); + assert(s->rate_limit.interval != 0); + return usec_add(s->rate_limit.begin, s->rate_limit.interval); + } + + /* Must be a time event source, if not ratelimited */ + if (EVENT_SOURCE_IS_TIME(s->type)) + return usec_add(s->time.next, s->time.accuracy); + + return USEC_INFINITY; +} + +static int latest_time_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Move the pending ones to the end */ + if (!x->pending && y->pending) + return -1; + if (x->pending && !y->pending) + return 1; + + /* Order by time */ + return CMP(time_event_source_latest(x), time_event_source_latest(y)); +} + +static int exit_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + assert(x->type == SOURCE_EXIT); + assert(y->type == SOURCE_EXIT); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Lower priority values first */ + return CMP(x->priority, y->priority); +} + +static void free_clock_data(struct clock_data *d) { + assert(d); + assert(d->wakeup == WAKEUP_CLOCK_DATA); + + safe_close(d->fd); + prioq_free(d->earliest); + prioq_free(d->latest); +} + +static sd_event *event_free(sd_event *e) { + sd_event_source *s; + + assert(e); + + while ((s = e->sources)) { + assert(s->floating); + source_disconnect(s); + sd_event_source_unref(s); + } + + assert(e->n_sources == 0); + + if (e->default_event_ptr) + *(e->default_event_ptr) = NULL; + + safe_close(e->epoll_fd); + safe_close(e->watchdog_fd); + + free_clock_data(&e->realtime); + free_clock_data(&e->boottime); + free_clock_data(&e->monotonic); + free_clock_data(&e->realtime_alarm); + free_clock_data(&e->boottime_alarm); + + prioq_free(e->pending); + prioq_free(e->prepare); + prioq_free(e->exit); + + free(e->signal_sources); + hashmap_free(e->signal_data); + + hashmap_free(e->inotify_data); + + hashmap_free(e->child_sources); + set_free(e->post_sources); + + free(e->event_queue); + + return mfree(e); +} + +_public_ int sd_event_new(sd_event** ret) { + sd_event *e; + int r; + + assert_return(ret, -EINVAL); + + e = new(sd_event, 1); + if (!e) + return -ENOMEM; + + *e = (sd_event) { + .n_ref = 1, + .epoll_fd = -1, + .watchdog_fd = -1, + .realtime.wakeup = WAKEUP_CLOCK_DATA, + .realtime.fd = -1, + .realtime.next = USEC_INFINITY, + .boottime.wakeup = WAKEUP_CLOCK_DATA, + .boottime.fd = -1, + .boottime.next = USEC_INFINITY, + .monotonic.wakeup = WAKEUP_CLOCK_DATA, + .monotonic.fd = -1, + .monotonic.next = USEC_INFINITY, + .realtime_alarm.wakeup = WAKEUP_CLOCK_DATA, + .realtime_alarm.fd = -1, + .realtime_alarm.next = USEC_INFINITY, + .boottime_alarm.wakeup = WAKEUP_CLOCK_DATA, + .boottime_alarm.fd = -1, + .boottime_alarm.next = USEC_INFINITY, + .perturb = USEC_INFINITY, + .original_pid = getpid_cached(), + }; + + r = prioq_ensure_allocated(&e->pending, pending_prioq_compare); + if (r < 0) + goto fail; + + e->epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (e->epoll_fd < 0) { + r = -errno; + goto fail; + } + + e->epoll_fd = fd_move_above_stdio(e->epoll_fd); + + if (secure_getenv("SD_EVENT_PROFILE_DELAYS")) { + log_debug("Event loop profiling enabled. Logarithmic histogram of event loop iterations in the range 2^0 … 2^63 us will be logged every 5s."); + e->profile_delays = true; + } + + *ret = e; + return 0; + +fail: + event_free(e); + return r; +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event, sd_event, event_free); + +_public_ sd_event_source* sd_event_source_disable_unref(sd_event_source *s) { + if (s) + (void) sd_event_source_set_enabled(s, SD_EVENT_OFF); + return sd_event_source_unref(s); +} + +static bool event_pid_changed(sd_event *e) { + assert(e); + + /* We don't support people creating an event loop and keeping + * it around over a fork(). Let's complain. */ + + return e->original_pid != getpid_cached(); +} + +static void source_io_unregister(sd_event_source *s) { + assert(s); + assert(s->type == SOURCE_IO); + + if (event_pid_changed(s->event)) + return; + + if (!s->io.registered) + return; + + if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->io.fd, NULL) < 0) + log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll, ignoring: %m", + strna(s->description), event_source_type_to_string(s->type)); + + s->io.registered = false; +} + +static int source_io_register( + sd_event_source *s, + int enabled, + uint32_t events) { + + assert(s); + assert(s->type == SOURCE_IO); + assert(enabled != SD_EVENT_OFF); + + struct epoll_event ev = { + .events = events | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0), + .data.ptr = s, + }; + + if (epoll_ctl(s->event->epoll_fd, + s->io.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, + s->io.fd, &ev) < 0) + return -errno; + + s->io.registered = true; + + return 0; +} + +static void source_child_pidfd_unregister(sd_event_source *s) { + assert(s); + assert(s->type == SOURCE_CHILD); + + if (event_pid_changed(s->event)) + return; + + if (!s->child.registered) + return; + + if (EVENT_SOURCE_WATCH_PIDFD(s)) + if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->child.pidfd, NULL) < 0) + log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll, ignoring: %m", + strna(s->description), event_source_type_to_string(s->type)); + + s->child.registered = false; +} + +static int source_child_pidfd_register(sd_event_source *s, int enabled) { + assert(s); + assert(s->type == SOURCE_CHILD); + assert(enabled != SD_EVENT_OFF); + + if (EVENT_SOURCE_WATCH_PIDFD(s)) { + struct epoll_event ev = { + .events = EPOLLIN | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0), + .data.ptr = s, + }; + + if (epoll_ctl(s->event->epoll_fd, + s->child.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, + s->child.pidfd, &ev) < 0) + return -errno; + } + + s->child.registered = true; + return 0; +} + +static clockid_t event_source_type_to_clock(EventSourceType t) { + + switch (t) { + + case SOURCE_TIME_REALTIME: + return CLOCK_REALTIME; + + case SOURCE_TIME_BOOTTIME: + return CLOCK_BOOTTIME; + + case SOURCE_TIME_MONOTONIC: + return CLOCK_MONOTONIC; + + case SOURCE_TIME_REALTIME_ALARM: + return CLOCK_REALTIME_ALARM; + + case SOURCE_TIME_BOOTTIME_ALARM: + return CLOCK_BOOTTIME_ALARM; + + default: + return (clockid_t) -1; + } +} + +static EventSourceType clock_to_event_source_type(clockid_t clock) { + + switch (clock) { + + case CLOCK_REALTIME: + return SOURCE_TIME_REALTIME; + + case CLOCK_BOOTTIME: + return SOURCE_TIME_BOOTTIME; + + case CLOCK_MONOTONIC: + return SOURCE_TIME_MONOTONIC; + + case CLOCK_REALTIME_ALARM: + return SOURCE_TIME_REALTIME_ALARM; + + case CLOCK_BOOTTIME_ALARM: + return SOURCE_TIME_BOOTTIME_ALARM; + + default: + return _SOURCE_EVENT_SOURCE_TYPE_INVALID; + } +} + +static struct clock_data* event_get_clock_data(sd_event *e, EventSourceType t) { + assert(e); + + switch (t) { + + case SOURCE_TIME_REALTIME: + return &e->realtime; + + case SOURCE_TIME_BOOTTIME: + return &e->boottime; + + case SOURCE_TIME_MONOTONIC: + return &e->monotonic; + + case SOURCE_TIME_REALTIME_ALARM: + return &e->realtime_alarm; + + case SOURCE_TIME_BOOTTIME_ALARM: + return &e->boottime_alarm; + + default: + return NULL; + } +} + +static void event_free_signal_data(sd_event *e, struct signal_data *d) { + assert(e); + + if (!d) + return; + + hashmap_remove(e->signal_data, &d->priority); + safe_close(d->fd); + free(d); +} + +static int event_make_signal_data( + sd_event *e, + int sig, + struct signal_data **ret) { + + struct signal_data *d; + bool added = false; + sigset_t ss_copy; + int64_t priority; + int r; + + assert(e); + + if (event_pid_changed(e)) + return -ECHILD; + + if (e->signal_sources && e->signal_sources[sig]) + priority = e->signal_sources[sig]->priority; + else + priority = SD_EVENT_PRIORITY_NORMAL; + + d = hashmap_get(e->signal_data, &priority); + if (d) { + if (sigismember(&d->sigset, sig) > 0) { + if (ret) + *ret = d; + return 0; + } + } else { + d = new(struct signal_data, 1); + if (!d) + return -ENOMEM; + + *d = (struct signal_data) { + .wakeup = WAKEUP_SIGNAL_DATA, + .fd = -1, + .priority = priority, + }; + + r = hashmap_ensure_put(&e->signal_data, &uint64_hash_ops, &d->priority, d); + if (r < 0) { + free(d); + return r; + } + + added = true; + } + + ss_copy = d->sigset; + assert_se(sigaddset(&ss_copy, sig) >= 0); + + r = signalfd(d->fd, &ss_copy, SFD_NONBLOCK|SFD_CLOEXEC); + if (r < 0) { + r = -errno; + goto fail; + } + + d->sigset = ss_copy; + + if (d->fd >= 0) { + if (ret) + *ret = d; + return 0; + } + + d->fd = fd_move_above_stdio(r); + + struct epoll_event ev = { + .events = EPOLLIN, + .data.ptr = d, + }; + + if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, d->fd, &ev) < 0) { + r = -errno; + goto fail; + } + + if (ret) + *ret = d; + + return 0; + +fail: + if (added) + event_free_signal_data(e, d); + + return r; +} + +static void event_unmask_signal_data(sd_event *e, struct signal_data *d, int sig) { + assert(e); + assert(d); + + /* Turns off the specified signal in the signal data + * object. If the signal mask of the object becomes empty that + * way removes it. */ + + if (sigismember(&d->sigset, sig) == 0) + return; + + assert_se(sigdelset(&d->sigset, sig) >= 0); + + if (sigisemptyset(&d->sigset)) { + /* If all the mask is all-zero we can get rid of the structure */ + event_free_signal_data(e, d); + return; + } + + assert(d->fd >= 0); + + if (signalfd(d->fd, &d->sigset, SFD_NONBLOCK|SFD_CLOEXEC) < 0) + log_debug_errno(errno, "Failed to unset signal bit, ignoring: %m"); +} + +static void event_gc_signal_data(sd_event *e, const int64_t *priority, int sig) { + struct signal_data *d; + static const int64_t zero_priority = 0; + + assert(e); + + /* Rechecks if the specified signal is still something we are interested in. If not, we'll unmask it, + * and possibly drop the signalfd for it. */ + + if (sig == SIGCHLD && + e->n_online_child_sources > 0) + return; + + if (e->signal_sources && + e->signal_sources[sig] && + event_source_is_online(e->signal_sources[sig])) + return; + + /* + * The specified signal might be enabled in three different queues: + * + * 1) the one that belongs to the priority passed (if it is non-NULL) + * 2) the one that belongs to the priority of the event source of the signal (if there is one) + * 3) the 0 priority (to cover the SIGCHLD case) + * + * Hence, let's remove it from all three here. + */ + + if (priority) { + d = hashmap_get(e->signal_data, priority); + if (d) + event_unmask_signal_data(e, d, sig); + } + + if (e->signal_sources && e->signal_sources[sig]) { + d = hashmap_get(e->signal_data, &e->signal_sources[sig]->priority); + if (d) + event_unmask_signal_data(e, d, sig); + } + + d = hashmap_get(e->signal_data, &zero_priority); + if (d) + event_unmask_signal_data(e, d, sig); +} + +static void event_source_pp_prioq_reshuffle(sd_event_source *s) { + assert(s); + + /* Reshuffles the pending + prepare prioqs. Called whenever the dispatch order changes, i.e. when + * they are enabled/disabled or marked pending and such. */ + + if (s->pending) + prioq_reshuffle(s->event->pending, s, &s->pending_index); + + if (s->prepare) + prioq_reshuffle(s->event->prepare, s, &s->prepare_index); +} + +static void event_source_time_prioq_reshuffle(sd_event_source *s) { + struct clock_data *d; + + assert(s); + + /* Called whenever the event source's timer ordering properties changed, i.e. time, accuracy, + * pending, enable state. Makes sure the two prioq's are ordered properly again. */ + + if (s->ratelimited) + d = &s->event->monotonic; + else { + assert(EVENT_SOURCE_IS_TIME(s->type)); + assert_se(d = event_get_clock_data(s->event, s->type)); + } + + prioq_reshuffle(d->earliest, s, &s->earliest_index); + prioq_reshuffle(d->latest, s, &s->latest_index); + d->needs_rearm = true; +} + +static void event_source_time_prioq_remove( + sd_event_source *s, + struct clock_data *d) { + + assert(s); + assert(d); + + prioq_remove(d->earliest, s, &s->earliest_index); + prioq_remove(d->latest, s, &s->latest_index); + s->earliest_index = s->latest_index = PRIOQ_IDX_NULL; + d->needs_rearm = true; +} + +static void source_disconnect(sd_event_source *s) { + sd_event *event; + + assert(s); + + if (!s->event) + return; + + assert(s->event->n_sources > 0); + + switch (s->type) { + + case SOURCE_IO: + if (s->io.fd >= 0) + source_io_unregister(s); + + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: + /* Only remove this event source from the time event source here if it is not ratelimited. If + * it is ratelimited, we'll remove it below, separately. Why? Because the clock used might + * differ: ratelimiting always uses CLOCK_MONOTONIC, but timer events might use any clock */ + + if (!s->ratelimited) { + struct clock_data *d; + assert_se(d = event_get_clock_data(s->event, s->type)); + event_source_time_prioq_remove(s, d); + } + + break; + + case SOURCE_SIGNAL: + if (s->signal.sig > 0) { + + if (s->event->signal_sources) + s->event->signal_sources[s->signal.sig] = NULL; + + event_gc_signal_data(s->event, &s->priority, s->signal.sig); + } + + break; + + case SOURCE_CHILD: + if (s->child.pid > 0) { + if (event_source_is_online(s)) { + assert(s->event->n_online_child_sources > 0); + s->event->n_online_child_sources--; + } + + (void) hashmap_remove(s->event->child_sources, PID_TO_PTR(s->child.pid)); + } + + if (EVENT_SOURCE_WATCH_PIDFD(s)) + source_child_pidfd_unregister(s); + else + event_gc_signal_data(s->event, &s->priority, SIGCHLD); + + break; + + case SOURCE_DEFER: + /* nothing */ + break; + + case SOURCE_POST: + set_remove(s->event->post_sources, s); + break; + + case SOURCE_EXIT: + prioq_remove(s->event->exit, s, &s->exit.prioq_index); + break; + + case SOURCE_INOTIFY: { + struct inode_data *inode_data; + + inode_data = s->inotify.inode_data; + if (inode_data) { + struct inotify_data *inotify_data; + assert_se(inotify_data = inode_data->inotify_data); + + /* Detach this event source from the inode object */ + LIST_REMOVE(inotify.by_inode_data, inode_data->event_sources, s); + s->inotify.inode_data = NULL; + + if (s->pending) { + assert(inotify_data->n_pending > 0); + inotify_data->n_pending--; + } + + /* Note that we don't reduce the inotify mask for the watch descriptor here if the inode is + * continued to being watched. That's because inotify doesn't really have an API for that: we + * can only change watch masks with access to the original inode either by fd or by path. But + * paths aren't stable, and keeping an O_PATH fd open all the time would mean wasting an fd + * continuously and keeping the mount busy which we can't really do. We could reconstruct the + * original inode from /proc/self/fdinfo/$INOTIFY_FD (as all watch descriptors are listed + * there), but given the need for open_by_handle_at() which is privileged and not universally + * available this would be quite an incomplete solution. Hence we go the other way, leave the + * mask set, even if it is not minimized now, and ignore all events we aren't interested in + * anymore after reception. Yes, this sucks, but … Linux … */ + + /* Maybe release the inode data (and its inotify) */ + event_gc_inode_data(s->event, inode_data); + } + + break; + } + + default: + assert_not_reached("Wut? I shouldn't exist."); + } + + if (s->pending) + prioq_remove(s->event->pending, s, &s->pending_index); + + if (s->prepare) + prioq_remove(s->event->prepare, s, &s->prepare_index); + + if (s->ratelimited) + event_source_time_prioq_remove(s, &s->event->monotonic); + + event = TAKE_PTR(s->event); + LIST_REMOVE(sources, event->sources, s); + event->n_sources--; + + /* Note that we don't invalidate the type here, since we still need it in order to close the fd or + * pidfd associated with this event source, which we'll do only on source_free(). */ + + if (!s->floating) + sd_event_unref(event); +} + +static sd_event_source* source_free(sd_event_source *s) { + assert(s); + + source_disconnect(s); + + if (s->type == SOURCE_IO && s->io.owned) + s->io.fd = safe_close(s->io.fd); + + if (s->type == SOURCE_CHILD) { + /* Eventually the kernel will do this automatically for us, but for now let's emulate this (unreliably) in userspace. */ + + if (s->child.process_owned) { + + if (!s->child.exited) { + bool sent = false; + + if (s->child.pidfd >= 0) { + if (pidfd_send_signal(s->child.pidfd, SIGKILL, NULL, 0) < 0) { + if (errno == ESRCH) /* Already dead */ + sent = true; + else if (!ERRNO_IS_NOT_SUPPORTED(errno)) + log_debug_errno(errno, "Failed to kill process " PID_FMT " via pidfd_send_signal(), re-trying via kill(): %m", + s->child.pid); + } else + sent = true; + } + + if (!sent) + if (kill(s->child.pid, SIGKILL) < 0) + if (errno != ESRCH) /* Already dead */ + log_debug_errno(errno, "Failed to kill process " PID_FMT " via kill(), ignoring: %m", + s->child.pid); + } + + if (!s->child.waited) { + siginfo_t si = {}; + + /* Reap the child if we can */ + (void) waitid(P_PID, s->child.pid, &si, WEXITED); + } + } + + if (s->child.pidfd_owned) + s->child.pidfd = safe_close(s->child.pidfd); + } + + if (s->destroy_callback) + s->destroy_callback(s->userdata); + + free(s->description); + return mfree(s); +} +DEFINE_TRIVIAL_CLEANUP_FUNC(sd_event_source*, source_free); + +static int source_set_pending(sd_event_source *s, bool b) { + int r; + + assert(s); + assert(s->type != SOURCE_EXIT); + + if (s->pending == b) + return 0; + + s->pending = b; + + if (b) { + s->pending_iteration = s->event->iteration; + + r = prioq_put(s->event->pending, s, &s->pending_index); + if (r < 0) { + s->pending = false; + return r; + } + } else + assert_se(prioq_remove(s->event->pending, s, &s->pending_index)); + + if (EVENT_SOURCE_IS_TIME(s->type)) + event_source_time_prioq_reshuffle(s); + + if (s->type == SOURCE_SIGNAL && !b) { + struct signal_data *d; + + d = hashmap_get(s->event->signal_data, &s->priority); + if (d && d->current == s) + d->current = NULL; + } + + if (s->type == SOURCE_INOTIFY) { + + assert(s->inotify.inode_data); + assert(s->inotify.inode_data->inotify_data); + + if (b) + s->inotify.inode_data->inotify_data->n_pending ++; + else { + assert(s->inotify.inode_data->inotify_data->n_pending > 0); + s->inotify.inode_data->inotify_data->n_pending --; + } + } + + return 1; +} + +static sd_event_source *source_new(sd_event *e, bool floating, EventSourceType type) { + sd_event_source *s; + + assert(e); + + s = new(sd_event_source, 1); + if (!s) + return NULL; + + *s = (struct sd_event_source) { + .n_ref = 1, + .event = e, + .floating = floating, + .type = type, + .pending_index = PRIOQ_IDX_NULL, + .prepare_index = PRIOQ_IDX_NULL, + }; + + if (!floating) + sd_event_ref(e); + + LIST_PREPEND(sources, e->sources, s); + e->n_sources++; + + return s; +} + +static int io_exit_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +_public_ int sd_event_add_io( + sd_event *e, + sd_event_source **ret, + int fd, + uint32_t events, + sd_event_io_handler_t callback, + void *userdata) { + + _cleanup_(source_freep) sd_event_source *s = NULL; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(fd >= 0, -EBADF); + assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = io_exit_callback; + + s = source_new(e, !ret, SOURCE_IO); + if (!s) + return -ENOMEM; + + s->wakeup = WAKEUP_EVENT_SOURCE; + s->io.fd = fd; + s->io.events = events; + s->io.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ON; + + r = source_io_register(s, s->enabled, events); + if (r < 0) + return r; + + if (ret) + *ret = s; + TAKE_PTR(s); + + return 0; +} + +static void initialize_perturb(sd_event *e) { + sd_id128_t bootid = {}; + + /* When we sleep for longer, we try to realign the wakeup to + the same time within each minute/second/250ms, so that + events all across the system can be coalesced into a single + CPU wakeup. However, let's take some system-specific + randomness for this value, so that in a network of systems + with synced clocks timer events are distributed a + bit. Here, we calculate a perturbation usec offset from the + boot ID. */ + + if (_likely_(e->perturb != USEC_INFINITY)) + return; + + if (sd_id128_get_boot(&bootid) >= 0) + e->perturb = (bootid.qwords[0] ^ bootid.qwords[1]) % USEC_PER_MINUTE; +} + +static int event_setup_timer_fd( + sd_event *e, + struct clock_data *d, + clockid_t clock) { + + assert(e); + assert(d); + + if (_likely_(d->fd >= 0)) + return 0; + + _cleanup_close_ int fd = -1; + + fd = timerfd_create(clock, TFD_NONBLOCK|TFD_CLOEXEC); + if (fd < 0) + return -errno; + + fd = fd_move_above_stdio(fd); + + struct epoll_event ev = { + .events = EPOLLIN, + .data.ptr = d, + }; + + if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) + return -errno; + + d->fd = TAKE_FD(fd); + return 0; +} + +static int time_exit_callback(sd_event_source *s, uint64_t usec, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +static int setup_clock_data(sd_event *e, struct clock_data *d, clockid_t clock) { + int r; + + assert(d); + + if (d->fd < 0) { + r = event_setup_timer_fd(e, d, clock); + if (r < 0) + return r; + } + + r = prioq_ensure_allocated(&d->earliest, earliest_time_prioq_compare); + if (r < 0) + return r; + + r = prioq_ensure_allocated(&d->latest, latest_time_prioq_compare); + if (r < 0) + return r; + + return 0; +} + +static int event_source_time_prioq_put( + sd_event_source *s, + struct clock_data *d) { + + int r; + + assert(s); + assert(d); + + r = prioq_put(d->earliest, s, &s->earliest_index); + if (r < 0) + return r; + + r = prioq_put(d->latest, s, &s->latest_index); + if (r < 0) { + assert_se(prioq_remove(d->earliest, s, &s->earliest_index) > 0); + s->earliest_index = PRIOQ_IDX_NULL; + return r; + } + + d->needs_rearm = true; + return 0; +} + +_public_ int sd_event_add_time( + sd_event *e, + sd_event_source **ret, + clockid_t clock, + uint64_t usec, + uint64_t accuracy, + sd_event_time_handler_t callback, + void *userdata) { + + EventSourceType type; + _cleanup_(source_freep) sd_event_source *s = NULL; + struct clock_data *d; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(accuracy != UINT64_MAX, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!clock_supported(clock)) /* Checks whether the kernel supports the clock */ + return -EOPNOTSUPP; + + type = clock_to_event_source_type(clock); /* checks whether sd-event supports this clock */ + if (type < 0) + return -EOPNOTSUPP; + + if (!callback) + callback = time_exit_callback; + + assert_se(d = event_get_clock_data(e, type)); + + r = setup_clock_data(e, d, clock); + if (r < 0) + return r; + + s = source_new(e, !ret, type); + if (!s) + return -ENOMEM; + + s->time.next = usec; + s->time.accuracy = accuracy == 0 ? DEFAULT_ACCURACY_USEC : accuracy; + s->time.callback = callback; + s->earliest_index = s->latest_index = PRIOQ_IDX_NULL; + s->userdata = userdata; + s->enabled = SD_EVENT_ONESHOT; + + r = event_source_time_prioq_put(s, d); + if (r < 0) + return r; + + if (ret) + *ret = s; + TAKE_PTR(s); + + return 0; +} + +#if 0 /* NM_IGNORED */ +_public_ int sd_event_add_time_relative( + sd_event *e, + sd_event_source **ret, + clockid_t clock, + uint64_t usec, + uint64_t accuracy, + sd_event_time_handler_t callback, + void *userdata) { + + usec_t t; + int r; + + /* Same as sd_event_add_time() but operates relative to the event loop's current point in time, and + * checks for overflow. */ + + r = sd_event_now(e, clock, &t); + if (r < 0) + return r; + + if (usec >= USEC_INFINITY - t) + return -EOVERFLOW; + + return sd_event_add_time(e, ret, clock, t + usec, accuracy, callback, userdata); +} + +static int signal_exit_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +_public_ int sd_event_add_signal( + sd_event *e, + sd_event_source **ret, + int sig, + sd_event_signal_handler_t callback, + void *userdata) { + + _cleanup_(source_freep) sd_event_source *s = NULL; + struct signal_data *d; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(SIGNAL_VALID(sig), -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = signal_exit_callback; + + r = signal_is_blocked(sig); + if (r < 0) + return r; + if (r == 0) + return -EBUSY; + + if (!e->signal_sources) { + e->signal_sources = new0(sd_event_source*, _NSIG); + if (!e->signal_sources) + return -ENOMEM; + } else if (e->signal_sources[sig]) + return -EBUSY; + + s = source_new(e, !ret, SOURCE_SIGNAL); + if (!s) + return -ENOMEM; + + s->signal.sig = sig; + s->signal.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ON; + + e->signal_sources[sig] = s; + + r = event_make_signal_data(e, sig, &d); + if (r < 0) + return r; + + /* Use the signal name as description for the event source by default */ + (void) sd_event_source_set_description(s, signal_to_string(sig)); + + if (ret) + *ret = s; + TAKE_PTR(s); + + return 0; +} + +static int child_exit_callback(sd_event_source *s, const siginfo_t *si, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +static bool shall_use_pidfd(void) { + /* Mostly relevant for debugging, i.e. this is used in test-event.c to test the event loop once with and once without pidfd */ + return getenv_bool_secure("SYSTEMD_PIDFD") != 0; +} + +_public_ int sd_event_add_child( + sd_event *e, + sd_event_source **ret, + pid_t pid, + int options, + sd_event_child_handler_t callback, + void *userdata) { + + _cleanup_(source_freep) sd_event_source *s = NULL; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(pid > 1, -EINVAL); + assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL); + assert_return(options != 0, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = child_exit_callback; + + if (e->n_online_child_sources == 0) { + /* Caller must block SIGCHLD before using us to watch children, even if pidfd is available, + * for compatibility with pre-pidfd and because we don't want the reap the child processes + * ourselves, i.e. call waitid(), and don't want Linux' default internal logic for that to + * take effect. + * + * (As an optimization we only do this check on the first child event source created.) */ + r = signal_is_blocked(SIGCHLD); + if (r < 0) + return r; + if (r == 0) + return -EBUSY; + } + + r = hashmap_ensure_allocated(&e->child_sources, NULL); + if (r < 0) + return r; + + if (hashmap_contains(e->child_sources, PID_TO_PTR(pid))) + return -EBUSY; + + s = source_new(e, !ret, SOURCE_CHILD); + if (!s) + return -ENOMEM; + + s->wakeup = WAKEUP_EVENT_SOURCE; + s->child.pid = pid; + s->child.options = options; + s->child.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ONESHOT; + + /* We always take a pidfd here if we can, even if we wait for anything else than WEXITED, so that we + * pin the PID, and make regular waitid() handling race-free. */ + + if (shall_use_pidfd()) { + s->child.pidfd = pidfd_open(s->child.pid, 0); + if (s->child.pidfd < 0) { + /* Propagate errors unless the syscall is not supported or blocked */ + if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) + return -errno; + } else + s->child.pidfd_owned = true; /* If we allocate the pidfd we own it by default */ + } else + s->child.pidfd = -1; + + r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s); + if (r < 0) + return r; + + if (EVENT_SOURCE_WATCH_PIDFD(s)) { + /* We have a pidfd and we only want to watch for exit */ + r = source_child_pidfd_register(s, s->enabled); + if (r < 0) + return r; + + } else { + /* We have no pidfd or we shall wait for some other event than WEXITED */ + r = event_make_signal_data(e, SIGCHLD, NULL); + if (r < 0) + return r; + + e->need_process_child = true; + } + + e->n_online_child_sources++; + + if (ret) + *ret = s; + TAKE_PTR(s); + return 0; +} + +_public_ int sd_event_add_child_pidfd( + sd_event *e, + sd_event_source **ret, + int pidfd, + int options, + sd_event_child_handler_t callback, + void *userdata) { + + + _cleanup_(source_freep) sd_event_source *s = NULL; + pid_t pid; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(pidfd >= 0, -EBADF); + assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL); + assert_return(options != 0, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = child_exit_callback; + + if (e->n_online_child_sources == 0) { + r = signal_is_blocked(SIGCHLD); + if (r < 0) + return r; + if (r == 0) + return -EBUSY; + } + + r = hashmap_ensure_allocated(&e->child_sources, NULL); + if (r < 0) + return r; + + r = pidfd_get_pid(pidfd, &pid); + if (r < 0) + return r; + + if (hashmap_contains(e->child_sources, PID_TO_PTR(pid))) + return -EBUSY; + + s = source_new(e, !ret, SOURCE_CHILD); + if (!s) + return -ENOMEM; + + s->wakeup = WAKEUP_EVENT_SOURCE; + s->child.pidfd = pidfd; + s->child.pid = pid; + s->child.options = options; + s->child.callback = callback; + s->child.pidfd_owned = false; /* If we got the pidfd passed in we don't own it by default (similar to the IO fd case) */ + s->userdata = userdata; + s->enabled = SD_EVENT_ONESHOT; + + r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s); + if (r < 0) + return r; + + if (EVENT_SOURCE_WATCH_PIDFD(s)) { + /* We only want to watch for WEXITED */ + r = source_child_pidfd_register(s, s->enabled); + if (r < 0) + return r; + } else { + /* We shall wait for some other event than WEXITED */ + r = event_make_signal_data(e, SIGCHLD, NULL); + if (r < 0) + return r; + + e->need_process_child = true; + } + + e->n_online_child_sources++; + + if (ret) + *ret = s; + TAKE_PTR(s); + return 0; +} +#endif /* NM_IGNORED */ + +static int generic_exit_callback(sd_event_source *s, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +_public_ int sd_event_add_defer( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + _cleanup_(source_freep) sd_event_source *s = NULL; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = generic_exit_callback; + + s = source_new(e, !ret, SOURCE_DEFER); + if (!s) + return -ENOMEM; + + s->defer.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ONESHOT; + + r = source_set_pending(s, true); + if (r < 0) + return r; + + if (ret) + *ret = s; + TAKE_PTR(s); + + return 0; +} + +_public_ int sd_event_add_post( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + _cleanup_(source_freep) sd_event_source *s = NULL; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = generic_exit_callback; + + s = source_new(e, !ret, SOURCE_POST); + if (!s) + return -ENOMEM; + + s->post.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ON; + + r = set_ensure_put(&e->post_sources, NULL, s); + if (r < 0) + return r; + assert(r > 0); + + if (ret) + *ret = s; + TAKE_PTR(s); + + return 0; +} + +_public_ int sd_event_add_exit( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + _cleanup_(source_freep) sd_event_source *s = NULL; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(callback, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + r = prioq_ensure_allocated(&e->exit, exit_prioq_compare); + if (r < 0) + return r; + + s = source_new(e, !ret, SOURCE_EXIT); + if (!s) + return -ENOMEM; + + s->exit.callback = callback; + s->userdata = userdata; + s->exit.prioq_index = PRIOQ_IDX_NULL; + s->enabled = SD_EVENT_ONESHOT; + + r = prioq_put(s->event->exit, s, &s->exit.prioq_index); + if (r < 0) + return r; + + if (ret) + *ret = s; + TAKE_PTR(s); + + return 0; +} + +static void event_free_inotify_data(sd_event *e, struct inotify_data *d) { + assert(e); + + if (!d) + return; + + assert(hashmap_isempty(d->inodes)); + assert(hashmap_isempty(d->wd)); + + if (d->buffer_filled > 0) + LIST_REMOVE(buffered, e->inotify_data_buffered, d); + + hashmap_free(d->inodes); + hashmap_free(d->wd); + + assert_se(hashmap_remove(e->inotify_data, &d->priority) == d); + + if (d->fd >= 0) { + if (epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, d->fd, NULL) < 0) + log_debug_errno(errno, "Failed to remove inotify fd from epoll, ignoring: %m"); + + safe_close(d->fd); + } + free(d); +} + +static int event_make_inotify_data( + sd_event *e, + int64_t priority, + struct inotify_data **ret) { + + _cleanup_close_ int fd = -1; + struct inotify_data *d; + int r; + + assert(e); + + d = hashmap_get(e->inotify_data, &priority); + if (d) { + if (ret) + *ret = d; + return 0; + } + + fd = inotify_init1(IN_NONBLOCK|O_CLOEXEC); + if (fd < 0) + return -errno; + + fd = fd_move_above_stdio(fd); + + d = new(struct inotify_data, 1); + if (!d) + return -ENOMEM; + + *d = (struct inotify_data) { + .wakeup = WAKEUP_INOTIFY_DATA, + .fd = TAKE_FD(fd), + .priority = priority, + }; + + r = hashmap_ensure_put(&e->inotify_data, &uint64_hash_ops, &d->priority, d); + if (r < 0) { + d->fd = safe_close(d->fd); + free(d); + return r; + } + + struct epoll_event ev = { + .events = EPOLLIN, + .data.ptr = d, + }; + + if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, d->fd, &ev) < 0) { + r = -errno; + d->fd = safe_close(d->fd); /* let's close this ourselves, as event_free_inotify_data() would otherwise + * remove the fd from the epoll first, which we don't want as we couldn't + * add it in the first place. */ + event_free_inotify_data(e, d); + return r; + } + + if (ret) + *ret = d; + + return 1; +} + +static int inode_data_compare(const struct inode_data *x, const struct inode_data *y) { + int r; + + assert(x); + assert(y); + + r = CMP(x->dev, y->dev); + if (r != 0) + return r; + + return CMP(x->ino, y->ino); +} + +static void inode_data_hash_func(const struct inode_data *d, struct siphash *state) { + assert(d); + + siphash24_compress(&d->dev, sizeof(d->dev), state); + siphash24_compress(&d->ino, sizeof(d->ino), state); +} + +DEFINE_PRIVATE_HASH_OPS(inode_data_hash_ops, struct inode_data, inode_data_hash_func, inode_data_compare); + +static void event_free_inode_data( + sd_event *e, + struct inode_data *d) { + + assert(e); + + if (!d) + return; + + assert(!d->event_sources); + + if (d->fd >= 0) { + LIST_REMOVE(to_close, e->inode_data_to_close, d); + safe_close(d->fd); + } + + if (d->inotify_data) { + + if (d->wd >= 0) { + if (d->inotify_data->fd >= 0) { + /* So here's a problem. At the time this runs the watch descriptor might already be + * invalidated, because an IN_IGNORED event might be queued right the moment we enter + * the syscall. Hence, whenever we get EINVAL, ignore it entirely, since it's a very + * likely case to happen. */ + + if (inotify_rm_watch(d->inotify_data->fd, d->wd) < 0 && errno != EINVAL) + log_debug_errno(errno, "Failed to remove watch descriptor %i from inotify, ignoring: %m", d->wd); + } + + assert_se(hashmap_remove(d->inotify_data->wd, INT_TO_PTR(d->wd)) == d); + } + + assert_se(hashmap_remove(d->inotify_data->inodes, d) == d); + } + + free(d); +} + +static void event_gc_inode_data( + sd_event *e, + struct inode_data *d) { + + struct inotify_data *inotify_data; + + assert(e); + + if (!d) + return; + + if (d->event_sources) + return; + + inotify_data = d->inotify_data; + event_free_inode_data(e, d); + + if (inotify_data && hashmap_isempty(inotify_data->inodes)) + event_free_inotify_data(e, inotify_data); +} + +static int event_make_inode_data( + sd_event *e, + struct inotify_data *inotify_data, + dev_t dev, + ino_t ino, + struct inode_data **ret) { + + struct inode_data *d, key; + int r; + + assert(e); + assert(inotify_data); + + key = (struct inode_data) { + .ino = ino, + .dev = dev, + }; + + d = hashmap_get(inotify_data->inodes, &key); + if (d) { + if (ret) + *ret = d; + + return 0; + } + + r = hashmap_ensure_allocated(&inotify_data->inodes, &inode_data_hash_ops); + if (r < 0) + return r; + + d = new(struct inode_data, 1); + if (!d) + return -ENOMEM; + + *d = (struct inode_data) { + .dev = dev, + .ino = ino, + .wd = -1, + .fd = -1, + .inotify_data = inotify_data, + }; + + r = hashmap_put(inotify_data->inodes, d, d); + if (r < 0) { + free(d); + return r; + } + + if (ret) + *ret = d; + + return 1; +} + +static uint32_t inode_data_determine_mask(struct inode_data *d) { + bool excl_unlink = true; + uint32_t combined = 0; + sd_event_source *s; + + assert(d); + + /* Combines the watch masks of all event sources watching this inode. We generally just OR them together, but + * the IN_EXCL_UNLINK flag is ANDed instead. + * + * Note that we add all sources to the mask here, regardless whether enabled, disabled or oneshot. That's + * because we cannot change the mask anymore after the event source was created once, since the kernel has no + * API for that. Hence we need to subscribe to the maximum mask we ever might be interested in, and suppress + * events we don't care for client-side. */ + + LIST_FOREACH(inotify.by_inode_data, s, d->event_sources) { + + if ((s->inotify.mask & IN_EXCL_UNLINK) == 0) + excl_unlink = false; + + combined |= s->inotify.mask; + } + + return (combined & ~(IN_ONESHOT|IN_DONT_FOLLOW|IN_ONLYDIR|IN_EXCL_UNLINK)) | (excl_unlink ? IN_EXCL_UNLINK : 0); +} + +static int inode_data_realize_watch(sd_event *e, struct inode_data *d) { + uint32_t combined_mask; + int wd, r; + + assert(d); + assert(d->fd >= 0); + + combined_mask = inode_data_determine_mask(d); + + if (d->wd >= 0 && combined_mask == d->combined_mask) + return 0; + + r = hashmap_ensure_allocated(&d->inotify_data->wd, NULL); + if (r < 0) + return r; + + wd = inotify_add_watch_fd(d->inotify_data->fd, d->fd, combined_mask); + if (wd < 0) + return -errno; + + if (d->wd < 0) { + r = hashmap_put(d->inotify_data->wd, INT_TO_PTR(wd), d); + if (r < 0) { + (void) inotify_rm_watch(d->inotify_data->fd, wd); + return r; + } + + d->wd = wd; + + } else if (d->wd != wd) { + + log_debug("Weird, the watch descriptor we already knew for this inode changed?"); + (void) inotify_rm_watch(d->fd, wd); + return -EINVAL; + } + + d->combined_mask = combined_mask; + return 1; +} + +static int inotify_exit_callback(sd_event_source *s, const struct inotify_event *event, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +_public_ int sd_event_add_inotify( + sd_event *e, + sd_event_source **ret, + const char *path, + uint32_t mask, + sd_event_inotify_handler_t callback, + void *userdata) { + + struct inotify_data *inotify_data = NULL; + struct inode_data *inode_data = NULL; + _cleanup_close_ int fd = -1; + _cleanup_(source_freep) sd_event_source *s = NULL; + struct stat st; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(path, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = inotify_exit_callback; + + /* Refuse IN_MASK_ADD since we coalesce watches on the same inode, and hence really don't want to merge + * masks. Or in other words, this whole code exists only to manage IN_MASK_ADD type operations for you, hence + * the user can't use them for us. */ + if (mask & IN_MASK_ADD) + return -EINVAL; + + fd = open(path, O_PATH|O_CLOEXEC| + (mask & IN_ONLYDIR ? O_DIRECTORY : 0)| + (mask & IN_DONT_FOLLOW ? O_NOFOLLOW : 0)); + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + + s = source_new(e, !ret, SOURCE_INOTIFY); + if (!s) + return -ENOMEM; + + s->enabled = mask & IN_ONESHOT ? SD_EVENT_ONESHOT : SD_EVENT_ON; + s->inotify.mask = mask; + s->inotify.callback = callback; + s->userdata = userdata; + + /* Allocate an inotify object for this priority, and an inode object within it */ + r = event_make_inotify_data(e, SD_EVENT_PRIORITY_NORMAL, &inotify_data); + if (r < 0) + return r; + + r = event_make_inode_data(e, inotify_data, st.st_dev, st.st_ino, &inode_data); + if (r < 0) { + event_free_inotify_data(e, inotify_data); + return r; + } + + /* Keep the O_PATH fd around until the first iteration of the loop, so that we can still change the priority of + * the event source, until then, for which we need the original inode. */ + if (inode_data->fd < 0) { + inode_data->fd = TAKE_FD(fd); + LIST_PREPEND(to_close, e->inode_data_to_close, inode_data); + } + + /* Link our event source to the inode data object */ + LIST_PREPEND(inotify.by_inode_data, inode_data->event_sources, s); + s->inotify.inode_data = inode_data; + + /* Actually realize the watch now */ + r = inode_data_realize_watch(e, inode_data); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s, path); + + if (ret) + *ret = s; + TAKE_PTR(s); + + return 0; +} + +static sd_event_source* event_source_free(sd_event_source *s) { + if (!s) + return NULL; + + /* Here's a special hack: when we are called from a + * dispatch handler we won't free the event source + * immediately, but we will detach the fd from the + * epoll. This way it is safe for the caller to unref + * the event source and immediately close the fd, but + * we still retain a valid event source object after + * the callback. */ + + if (s->dispatching) { + if (s->type == SOURCE_IO) + source_io_unregister(s); + + source_disconnect(s); + } else + source_free(s); + + return NULL; +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event_source, sd_event_source, event_source_free); + +_public_ int sd_event_source_set_description(sd_event_source *s, const char *description) { + assert_return(s, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return free_and_strdup(&s->description, description); +} + +_public_ int sd_event_source_get_description(sd_event_source *s, const char **description) { + assert_return(s, -EINVAL); + assert_return(description, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (!s->description) + return -ENXIO; + + *description = s->description; + return 0; +} + +_public_ sd_event *sd_event_source_get_event(sd_event_source *s) { + assert_return(s, NULL); + + return s->event; +} + +_public_ int sd_event_source_get_pending(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type != SOURCE_EXIT, -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return s->pending; +} + +_public_ int sd_event_source_get_io_fd(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return s->io.fd; +} + +_public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) { + int r; + + assert_return(s, -EINVAL); + assert_return(fd >= 0, -EBADF); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (s->io.fd == fd) + return 0; + + if (event_source_is_offline(s)) { + s->io.fd = fd; + s->io.registered = false; + } else { + int saved_fd; + + saved_fd = s->io.fd; + assert(s->io.registered); + + s->io.fd = fd; + s->io.registered = false; + + r = source_io_register(s, s->enabled, s->io.events); + if (r < 0) { + s->io.fd = saved_fd; + s->io.registered = true; + return r; + } + + (void) epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, saved_fd, NULL); + } + + return 0; +} + +_public_ int sd_event_source_get_io_fd_own(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + + return s->io.owned; +} + +_public_ int sd_event_source_set_io_fd_own(sd_event_source *s, int own) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + + s->io.owned = own; + return 0; +} + +_public_ int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events) { + assert_return(s, -EINVAL); + assert_return(events, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *events = s->io.events; + return 0; +} + +_public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events) { + int r; + + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + /* edge-triggered updates are never skipped, so we can reset edges */ + if (s->io.events == events && !(events & EPOLLET)) + return 0; + + r = source_set_pending(s, false); + if (r < 0) + return r; + + if (event_source_is_online(s)) { + r = source_io_register(s, s->enabled, events); + if (r < 0) + return r; + } + + s->io.events = events; + + return 0; +} + +_public_ int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents) { + assert_return(s, -EINVAL); + assert_return(revents, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(s->pending, -ENODATA); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *revents = s->io.revents; + return 0; +} + +_public_ int sd_event_source_get_signal(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_SIGNAL, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return s->signal.sig; +} + +_public_ int sd_event_source_get_priority(sd_event_source *s, int64_t *priority) { + assert_return(s, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *priority = s->priority; + return 0; +} + +_public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority) { + bool rm_inotify = false, rm_inode = false; + struct inotify_data *new_inotify_data = NULL; + struct inode_data *new_inode_data = NULL; + int r; + + assert_return(s, -EINVAL); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (s->priority == priority) + return 0; + + if (s->type == SOURCE_INOTIFY) { + struct inode_data *old_inode_data; + + assert(s->inotify.inode_data); + old_inode_data = s->inotify.inode_data; + + /* We need the original fd to change the priority. If we don't have it we can't change the priority, + * anymore. Note that we close any fds when entering the next event loop iteration, i.e. for inotify + * events we allow priority changes only until the first following iteration. */ + if (old_inode_data->fd < 0) + return -EOPNOTSUPP; + + r = event_make_inotify_data(s->event, priority, &new_inotify_data); + if (r < 0) + return r; + rm_inotify = r > 0; + + r = event_make_inode_data(s->event, new_inotify_data, old_inode_data->dev, old_inode_data->ino, &new_inode_data); + if (r < 0) + goto fail; + rm_inode = r > 0; + + if (new_inode_data->fd < 0) { + /* Duplicate the fd for the new inode object if we don't have any yet */ + new_inode_data->fd = fcntl(old_inode_data->fd, F_DUPFD_CLOEXEC, 3); + if (new_inode_data->fd < 0) { + r = -errno; + goto fail; + } + + LIST_PREPEND(to_close, s->event->inode_data_to_close, new_inode_data); + } + + /* Move the event source to the new inode data structure */ + LIST_REMOVE(inotify.by_inode_data, old_inode_data->event_sources, s); + LIST_PREPEND(inotify.by_inode_data, new_inode_data->event_sources, s); + s->inotify.inode_data = new_inode_data; + + /* Now create the new watch */ + r = inode_data_realize_watch(s->event, new_inode_data); + if (r < 0) { + /* Move it back */ + LIST_REMOVE(inotify.by_inode_data, new_inode_data->event_sources, s); + LIST_PREPEND(inotify.by_inode_data, old_inode_data->event_sources, s); + s->inotify.inode_data = old_inode_data; + goto fail; + } + + s->priority = priority; + + event_gc_inode_data(s->event, old_inode_data); + + } else if (s->type == SOURCE_SIGNAL && event_source_is_online(s)) { + struct signal_data *old, *d; + + /* Move us from the signalfd belonging to the old + * priority to the signalfd of the new priority */ + + assert_se(old = hashmap_get(s->event->signal_data, &s->priority)); + + s->priority = priority; + + r = event_make_signal_data(s->event, s->signal.sig, &d); + if (r < 0) { + s->priority = old->priority; + return r; + } + + event_unmask_signal_data(s->event, old, s->signal.sig); + } else + s->priority = priority; + + event_source_pp_prioq_reshuffle(s); + + if (s->type == SOURCE_EXIT) + prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); + + return 0; + +fail: + if (rm_inode) + event_free_inode_data(s->event, new_inode_data); + + if (rm_inotify) + event_free_inotify_data(s->event, new_inotify_data); + + return r; +} + +_public_ int sd_event_source_get_enabled(sd_event_source *s, int *ret) { + assert_return(s, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (ret) + *ret = s->enabled; + + return s->enabled != SD_EVENT_OFF; +} + +static int event_source_offline( + sd_event_source *s, + int enabled, + bool ratelimited) { + + bool was_offline; + int r; + + assert(s); + assert(enabled == SD_EVENT_OFF || ratelimited); + + /* Unset the pending flag when this event source is disabled */ + if (s->enabled != SD_EVENT_OFF && + enabled == SD_EVENT_OFF && + !IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) { + r = source_set_pending(s, false); + if (r < 0) + return r; + } + + was_offline = event_source_is_offline(s); + s->enabled = enabled; + s->ratelimited = ratelimited; + + switch (s->type) { + + case SOURCE_IO: + source_io_unregister(s); + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: + event_source_time_prioq_reshuffle(s); + break; + + case SOURCE_SIGNAL: + event_gc_signal_data(s->event, &s->priority, s->signal.sig); + break; + + case SOURCE_CHILD: + if (!was_offline) { + assert(s->event->n_online_child_sources > 0); + s->event->n_online_child_sources--; + } + + if (EVENT_SOURCE_WATCH_PIDFD(s)) + source_child_pidfd_unregister(s); + else + event_gc_signal_data(s->event, &s->priority, SIGCHLD); + break; + + case SOURCE_EXIT: + prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); + break; + + case SOURCE_DEFER: + case SOURCE_POST: + case SOURCE_INOTIFY: + break; + + default: + assert_not_reached("Wut? I shouldn't exist."); + } + + return 1; +} + +static int event_source_online( + sd_event_source *s, + int enabled, + bool ratelimited) { + + bool was_online; + int r; + + assert(s); + assert(enabled != SD_EVENT_OFF || !ratelimited); + + /* Unset the pending flag when this event source is enabled */ + if (s->enabled == SD_EVENT_OFF && + enabled != SD_EVENT_OFF && + !IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) { + r = source_set_pending(s, false); + if (r < 0) + return r; + } + + /* Are we really ready for onlining? */ + if (enabled == SD_EVENT_OFF || ratelimited) { + /* Nope, we are not ready for onlining, then just update the precise state and exit */ + s->enabled = enabled; + s->ratelimited = ratelimited; + return 0; + } + + was_online = event_source_is_online(s); + + switch (s->type) { + case SOURCE_IO: + r = source_io_register(s, enabled, s->io.events); + if (r < 0) + return r; + break; + + case SOURCE_SIGNAL: + r = event_make_signal_data(s->event, s->signal.sig, NULL); + if (r < 0) { + event_gc_signal_data(s->event, &s->priority, s->signal.sig); + return r; + } + + break; + + case SOURCE_CHILD: + if (EVENT_SOURCE_WATCH_PIDFD(s)) { + /* yes, we have pidfd */ + + r = source_child_pidfd_register(s, enabled); + if (r < 0) + return r; + } else { + /* no pidfd, or something other to watch for than WEXITED */ + + r = event_make_signal_data(s->event, SIGCHLD, NULL); + if (r < 0) { + event_gc_signal_data(s->event, &s->priority, SIGCHLD); + return r; + } + } + + if (!was_online) + s->event->n_online_child_sources++; + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: + case SOURCE_EXIT: + case SOURCE_DEFER: + case SOURCE_POST: + case SOURCE_INOTIFY: + break; + + default: + assert_not_reached("Wut? I shouldn't exist."); + } + + s->enabled = enabled; + s->ratelimited = ratelimited; + + /* Non-failing operations below */ + switch (s->type) { + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: + event_source_time_prioq_reshuffle(s); + break; + + case SOURCE_EXIT: + prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); + break; + + default: + break; + } + + return 1; +} + +_public_ int sd_event_source_set_enabled(sd_event_source *s, int m) { + int r; + + assert_return(s, -EINVAL); + assert_return(IN_SET(m, SD_EVENT_OFF, SD_EVENT_ON, SD_EVENT_ONESHOT), -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + /* If we are dead anyway, we are fine with turning off sources, but everything else needs to fail. */ + if (s->event->state == SD_EVENT_FINISHED) + return m == SD_EVENT_OFF ? 0 : -ESTALE; + + if (s->enabled == m) /* No change? */ + return 0; + + if (m == SD_EVENT_OFF) + r = event_source_offline(s, m, s->ratelimited); + else { + if (s->enabled != SD_EVENT_OFF) { + /* Switching from "on" to "oneshot" or back? If that's the case, we can take a shortcut, the + * event source is already enabled after all. */ + s->enabled = m; + return 0; + } + + r = event_source_online(s, m, s->ratelimited); + } + if (r < 0) + return r; + + event_source_pp_prioq_reshuffle(s); + return 0; +} + +_public_ int sd_event_source_get_time(sd_event_source *s, uint64_t *usec) { + assert_return(s, -EINVAL); + assert_return(usec, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *usec = s->time.next; + return 0; +} + +_public_ int sd_event_source_set_time(sd_event_source *s, uint64_t usec) { + int r; + + assert_return(s, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + r = source_set_pending(s, false); + if (r < 0) + return r; + + s->time.next = usec; + + event_source_time_prioq_reshuffle(s); + return 0; +} + +_public_ int sd_event_source_set_time_relative(sd_event_source *s, uint64_t usec) { + usec_t t; + int r; + + assert_return(s, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + + r = sd_event_now(s->event, event_source_type_to_clock(s->type), &t); + if (r < 0) + return r; + + usec = usec_add(t, usec); + if (usec == USEC_INFINITY) + return -EOVERFLOW; + + return sd_event_source_set_time(s, usec); +} + +_public_ int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec) { + assert_return(s, -EINVAL); + assert_return(usec, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *usec = s->time.accuracy; + return 0; +} + +_public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec) { + int r; + + assert_return(s, -EINVAL); + assert_return(usec != UINT64_MAX, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + r = source_set_pending(s, false); + if (r < 0) + return r; + + if (usec == 0) + usec = DEFAULT_ACCURACY_USEC; + + s->time.accuracy = usec; + + event_source_time_prioq_reshuffle(s); + return 0; +} + +_public_ int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock) { + assert_return(s, -EINVAL); + assert_return(clock, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *clock = event_source_type_to_clock(s->type); + return 0; +} + +_public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid) { + assert_return(s, -EINVAL); + assert_return(pid, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *pid = s->child.pid; + return 0; +} + +_public_ int sd_event_source_get_child_pidfd(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (s->child.pidfd < 0) + return -EOPNOTSUPP; + + return s->child.pidfd; +} + +_public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, const siginfo_t *si, unsigned flags) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + assert_return(SIGNAL_VALID(sig), -EINVAL); + + /* If we already have seen indication the process exited refuse sending a signal early. This way we + * can be sure we don't accidentally kill the wrong process on PID reuse when pidfds are not + * available. */ + if (s->child.exited) + return -ESRCH; + + if (s->child.pidfd >= 0) { + siginfo_t copy; + + /* pidfd_send_signal() changes the siginfo_t argument. This is weird, let's hence copy the + * structure here */ + if (si) + copy = *si; + + if (pidfd_send_signal(s->child.pidfd, sig, si ? © : NULL, 0) < 0) { + /* Let's propagate the error only if the system call is not implemented or prohibited */ + if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) + return -errno; + } else + return 0; + } + + /* Flags are only supported for pidfd_send_signal(), not for rt_sigqueueinfo(), hence let's refuse + * this here. */ + if (flags != 0) + return -EOPNOTSUPP; + + if (si) { + /* We use rt_sigqueueinfo() only if siginfo_t is specified. */ + siginfo_t copy = *si; + + if (rt_sigqueueinfo(s->child.pid, sig, ©) < 0) + return -errno; + } else if (kill(s->child.pid, sig) < 0) + return -errno; + + return 0; +} + +_public_ int sd_event_source_get_child_pidfd_own(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + + if (s->child.pidfd < 0) + return -EOPNOTSUPP; + + return s->child.pidfd_owned; +} + +_public_ int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + + if (s->child.pidfd < 0) + return -EOPNOTSUPP; + + s->child.pidfd_owned = own; + return 0; +} + +_public_ int sd_event_source_get_child_process_own(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + + return s->child.process_owned; +} + +_public_ int sd_event_source_set_child_process_own(sd_event_source *s, int own) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + + s->child.process_owned = own; + return 0; +} + +_public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *mask) { + assert_return(s, -EINVAL); + assert_return(mask, -EINVAL); + assert_return(s->type == SOURCE_INOTIFY, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *mask = s->inotify.mask; + return 0; +} + +_public_ int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback) { + int r; + + assert_return(s, -EINVAL); + assert_return(s->type != SOURCE_EXIT, -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (s->prepare == callback) + return 0; + + if (callback && s->prepare) { + s->prepare = callback; + return 0; + } + + r = prioq_ensure_allocated(&s->event->prepare, prepare_prioq_compare); + if (r < 0) + return r; + + s->prepare = callback; + + if (callback) { + r = prioq_put(s->event->prepare, s, &s->prepare_index); + if (r < 0) + return r; + } else + prioq_remove(s->event->prepare, s, &s->prepare_index); + + return 0; +} + +_public_ void* sd_event_source_get_userdata(sd_event_source *s) { + assert_return(s, NULL); + + return s->userdata; +} + +_public_ void *sd_event_source_set_userdata(sd_event_source *s, void *userdata) { + void *ret; + + assert_return(s, NULL); + + ret = s->userdata; + s->userdata = userdata; + + return ret; +} + +static int event_source_enter_ratelimited(sd_event_source *s) { + int r; + + assert(s); + + /* When an event source becomes ratelimited, we place it in the CLOCK_MONOTONIC priority queue, with + * the end of the rate limit time window, much as if it was a timer event source. */ + + if (s->ratelimited) + return 0; /* Already ratelimited, this is a NOP hence */ + + /* Make sure we can install a CLOCK_MONOTONIC event further down. */ + r = setup_clock_data(s->event, &s->event->monotonic, CLOCK_MONOTONIC); + if (r < 0) + return r; + + /* Timer event sources are already using the earliest/latest queues for the timer scheduling. Let's + * first remove them from the prioq appropriate for their own clock, so that we can use the prioq + * fields of the event source then for adding it to the CLOCK_MONOTONIC prioq instead. */ + if (EVENT_SOURCE_IS_TIME(s->type)) + event_source_time_prioq_remove(s, event_get_clock_data(s->event, s->type)); + + /* Now, let's add the event source to the monotonic clock instead */ + r = event_source_time_prioq_put(s, &s->event->monotonic); + if (r < 0) + goto fail; + + /* And let's take the event source officially offline */ + r = event_source_offline(s, s->enabled, /* ratelimited= */ true); + if (r < 0) { + event_source_time_prioq_remove(s, &s->event->monotonic); + goto fail; + } + + event_source_pp_prioq_reshuffle(s); + + log_debug("Event source %p (%s) entered rate limit state.", s, strna(s->description)); + return 0; + +fail: + /* Reinstall time event sources in the priority queue as before. This shouldn't fail, since the queue + * space for it should already be allocated. */ + if (EVENT_SOURCE_IS_TIME(s->type)) + assert_se(event_source_time_prioq_put(s, event_get_clock_data(s->event, s->type)) >= 0); + + return r; +} + +static int event_source_leave_ratelimit(sd_event_source *s) { + int r; + + assert(s); + + if (!s->ratelimited) + return 0; + + /* Let's take the event source out of the monotonic prioq first. */ + event_source_time_prioq_remove(s, &s->event->monotonic); + + /* Let's then add the event source to its native clock prioq again — if this is a timer event source */ + if (EVENT_SOURCE_IS_TIME(s->type)) { + r = event_source_time_prioq_put(s, event_get_clock_data(s->event, s->type)); + if (r < 0) + goto fail; + } + + /* Let's try to take it online again. */ + r = event_source_online(s, s->enabled, /* ratelimited= */ false); + if (r < 0) { + /* Do something roughly sensible when this failed: undo the two prioq ops above */ + if (EVENT_SOURCE_IS_TIME(s->type)) + event_source_time_prioq_remove(s, event_get_clock_data(s->event, s->type)); + + goto fail; + } + + event_source_pp_prioq_reshuffle(s); + ratelimit_reset(&s->rate_limit); + + log_debug("Event source %p (%s) left rate limit state.", s, strna(s->description)); + return 0; + +fail: + /* Do something somewhat reasonable when we cannot move an event sources out of ratelimited mode: + * simply put it back in it, maybe we can then process it more successfully next iteration. */ + assert_se(event_source_time_prioq_put(s, &s->event->monotonic) >= 0); + + return r; +} + +static usec_t sleep_between(sd_event *e, usec_t a, usec_t b) { + usec_t c; + assert(e); + assert(a <= b); + + if (a <= 0) + return 0; + if (a >= USEC_INFINITY) + return USEC_INFINITY; + + if (b <= a + 1) + return a; + + initialize_perturb(e); + + /* + Find a good time to wake up again between times a and b. We + have two goals here: + + a) We want to wake up as seldom as possible, hence prefer + later times over earlier times. + + b) But if we have to wake up, then let's make sure to + dispatch as much as possible on the entire system. + + We implement this by waking up everywhere at the same time + within any given minute if we can, synchronised via the + perturbation value determined from the boot ID. If we can't, + then we try to find the same spot in every 10s, then 1s and + then 250ms step. Otherwise, we pick the last possible time + to wake up. + */ + + c = (b / USEC_PER_MINUTE) * USEC_PER_MINUTE + e->perturb; + if (c >= b) { + if (_unlikely_(c < USEC_PER_MINUTE)) + return b; + + c -= USEC_PER_MINUTE; + } + + if (c >= a) + return c; + + c = (b / (USEC_PER_SEC*10)) * (USEC_PER_SEC*10) + (e->perturb % (USEC_PER_SEC*10)); + if (c >= b) { + if (_unlikely_(c < USEC_PER_SEC*10)) + return b; + + c -= USEC_PER_SEC*10; + } + + if (c >= a) + return c; + + c = (b / USEC_PER_SEC) * USEC_PER_SEC + (e->perturb % USEC_PER_SEC); + if (c >= b) { + if (_unlikely_(c < USEC_PER_SEC)) + return b; + + c -= USEC_PER_SEC; + } + + if (c >= a) + return c; + + c = (b / (USEC_PER_MSEC*250)) * (USEC_PER_MSEC*250) + (e->perturb % (USEC_PER_MSEC*250)); + if (c >= b) { + if (_unlikely_(c < USEC_PER_MSEC*250)) + return b; + + c -= USEC_PER_MSEC*250; + } + + if (c >= a) + return c; + + return b; +} + +static int event_arm_timer( + sd_event *e, + struct clock_data *d) { + + struct itimerspec its = {}; + sd_event_source *a, *b; + usec_t t; + + assert(e); + assert(d); + + if (!d->needs_rearm) + return 0; + else + d->needs_rearm = false; + + a = prioq_peek(d->earliest); + if (!a || a->enabled == SD_EVENT_OFF || time_event_source_next(a) == USEC_INFINITY) { + + if (d->fd < 0) + return 0; + + if (d->next == USEC_INFINITY) + return 0; + + /* disarm */ + if (timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) + return -errno; + + d->next = USEC_INFINITY; + return 0; + } + + b = prioq_peek(d->latest); + assert_se(b && b->enabled != SD_EVENT_OFF); + + t = sleep_between(e, time_event_source_next(a), time_event_source_latest(b)); + if (d->next == t) + return 0; + + assert_se(d->fd >= 0); + + if (t == 0) { + /* We don' want to disarm here, just mean some time looooong ago. */ + its.it_value.tv_sec = 0; + its.it_value.tv_nsec = 1; + } else + timespec_store(&its.it_value, t); + + if (timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) + return -errno; + + d->next = t; + return 0; +} + +static int process_io(sd_event *e, sd_event_source *s, uint32_t revents) { + assert(e); + assert(s); + assert(s->type == SOURCE_IO); + + /* If the event source was already pending, we just OR in the + * new revents, otherwise we reset the value. The ORing is + * necessary to handle EPOLLONESHOT events properly where + * readability might happen independently of writability, and + * we need to keep track of both */ + + if (s->pending) + s->io.revents |= revents; + else + s->io.revents = revents; + + return source_set_pending(s, true); +} + +static int flush_timer(sd_event *e, int fd, uint32_t events, usec_t *next) { + uint64_t x; + ssize_t ss; + + assert(e); + assert(fd >= 0); + + assert_return(events == EPOLLIN, -EIO); + + ss = read(fd, &x, sizeof(x)); + if (ss < 0) { + if (IN_SET(errno, EAGAIN, EINTR)) + return 0; + + return -errno; + } + + if (_unlikely_(ss != sizeof(x))) + return -EIO; + + if (next) + *next = USEC_INFINITY; + + return 0; +} + +static int process_timer( + sd_event *e, + usec_t n, + struct clock_data *d) { + + sd_event_source *s; + int r; + + assert(e); + assert(d); + + for (;;) { + s = prioq_peek(d->earliest); + if (!s || time_event_source_next(s) > n) + break; + + if (s->ratelimited) { + /* This is an event sources whose ratelimit window has ended. Let's turn it on + * again. */ + assert(s->ratelimited); + + r = event_source_leave_ratelimit(s); + if (r < 0) + return r; + + continue; + } + + if (s->enabled == SD_EVENT_OFF || s->pending) + break; + + r = source_set_pending(s, true); + if (r < 0) + return r; + + event_source_time_prioq_reshuffle(s); + } + + return 0; +} + +static int process_child(sd_event *e, int64_t threshold, int64_t *ret_min_priority) { + int64_t min_priority = threshold; + bool something_new = false; + sd_event_source *s; + int r; + + assert(e); + assert(ret_min_priority); + + if (!e->need_process_child) { + *ret_min_priority = min_priority; + return 0; + } + + e->need_process_child = false; + + /* + So, this is ugly. We iteratively invoke waitid() with P_PID + + WNOHANG for each PID we wait for, instead of using + P_ALL. This is because we only want to get child + information of very specific child processes, and not all + of them. We might not have processed the SIGCHLD even of a + previous invocation and we don't want to maintain a + unbounded *per-child* event queue, hence we really don't + want anything flushed out of the kernel's queue that we + don't care about. Since this is O(n) this means that if you + have a lot of processes you probably want to handle SIGCHLD + yourself. + + We do not reap the children here (by using WNOWAIT), this + is only done after the event source is dispatched so that + the callback still sees the process as a zombie. + */ + + HASHMAP_FOREACH(s, e->child_sources) { + assert(s->type == SOURCE_CHILD); + + if (s->priority > threshold) + continue; + + if (s->pending) + continue; + + if (event_source_is_offline(s)) + continue; + + if (s->child.exited) + continue; + + if (EVENT_SOURCE_WATCH_PIDFD(s)) /* There's a usable pidfd known for this event source? then don't waitid() for it here */ + continue; + + zero(s->child.siginfo); + if (waitid(P_PID, s->child.pid, &s->child.siginfo, + WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | s->child.options) < 0) + return negative_errno(); + + if (s->child.siginfo.si_pid != 0) { + bool zombie = IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED); + + if (zombie) + s->child.exited = true; + + if (!zombie && (s->child.options & WEXITED)) { + /* If the child isn't dead then let's + * immediately remove the state change + * from the queue, since there's no + * benefit in leaving it queued */ + + assert(s->child.options & (WSTOPPED|WCONTINUED)); + (void) waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|(s->child.options & (WSTOPPED|WCONTINUED))); + } + + r = source_set_pending(s, true); + if (r < 0) + return r; + if (r > 0) { + something_new = true; + min_priority = MIN(min_priority, s->priority); + } + } + } + + *ret_min_priority = min_priority; + return something_new; +} + +static int process_pidfd(sd_event *e, sd_event_source *s, uint32_t revents) { + assert(e); + assert(s); + assert(s->type == SOURCE_CHILD); + + if (s->pending) + return 0; + + if (event_source_is_offline(s)) + return 0; + + if (!EVENT_SOURCE_WATCH_PIDFD(s)) + return 0; + + zero(s->child.siginfo); + if (waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG | WNOWAIT | s->child.options) < 0) + return -errno; + + if (s->child.siginfo.si_pid == 0) + return 0; + + if (IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED)) + s->child.exited = true; + + return source_set_pending(s, true); +} + +static int process_signal(sd_event *e, struct signal_data *d, uint32_t events, int64_t *min_priority) { + int r; + + assert(e); + assert(d); + assert_return(events == EPOLLIN, -EIO); + assert(min_priority); + + /* If there's a signal queued on this priority and SIGCHLD is + on this priority too, then make sure to recheck the + children we watch. This is because we only ever dequeue + the first signal per priority, and if we dequeue one, and + SIGCHLD might be enqueued later we wouldn't know, but we + might have higher priority children we care about hence we + need to check that explicitly. */ + + if (sigismember(&d->sigset, SIGCHLD)) + e->need_process_child = true; + + /* If there's already an event source pending for this + * priority we don't read another */ + if (d->current) + return 0; + + for (;;) { + struct signalfd_siginfo si; + ssize_t n; + sd_event_source *s = NULL; + + n = read(d->fd, &si, sizeof(si)); + if (n < 0) { + if (IN_SET(errno, EAGAIN, EINTR)) + return 0; + + return -errno; + } + + if (_unlikely_(n != sizeof(si))) + return -EIO; + + assert(SIGNAL_VALID(si.ssi_signo)); + + if (e->signal_sources) + s = e->signal_sources[si.ssi_signo]; + if (!s) + continue; + if (s->pending) + continue; + + s->signal.siginfo = si; + d->current = s; + + r = source_set_pending(s, true); + if (r < 0) + return r; + if (r > 0 && *min_priority >= s->priority) { + *min_priority = s->priority; + return 1; /* an event source with smaller priority is queued. */ + } + + return 0; + } +} + +static int event_inotify_data_read(sd_event *e, struct inotify_data *d, uint32_t revents, int64_t threshold) { + ssize_t n; + + assert(e); + assert(d); + + assert_return(revents == EPOLLIN, -EIO); + + /* If there's already an event source pending for this priority, don't read another */ + if (d->n_pending > 0) + return 0; + + /* Is the read buffer non-empty? If so, let's not read more */ + if (d->buffer_filled > 0) + return 0; + + if (d->priority > threshold) + return 0; + + n = read(d->fd, &d->buffer, sizeof(d->buffer)); + if (n < 0) { + if (IN_SET(errno, EAGAIN, EINTR)) + return 0; + + return -errno; + } + + assert(n > 0); + d->buffer_filled = (size_t) n; + LIST_PREPEND(buffered, e->inotify_data_buffered, d); + + return 1; +} + +static void event_inotify_data_drop(sd_event *e, struct inotify_data *d, size_t sz) { + assert(e); + assert(d); + assert(sz <= d->buffer_filled); + + if (sz == 0) + return; + + /* Move the rest to the buffer to the front, in order to get things properly aligned again */ + memmove(d->buffer.raw, d->buffer.raw + sz, d->buffer_filled - sz); + d->buffer_filled -= sz; + + if (d->buffer_filled == 0) + LIST_REMOVE(buffered, e->inotify_data_buffered, d); +} + +static int event_inotify_data_process(sd_event *e, struct inotify_data *d) { + int r; + + assert(e); + assert(d); + + /* If there's already an event source pending for this priority, don't read another */ + if (d->n_pending > 0) + return 0; + + while (d->buffer_filled > 0) { + size_t sz; + + /* Let's validate that the event structures are complete */ + if (d->buffer_filled < offsetof(struct inotify_event, name)) + return -EIO; + + sz = offsetof(struct inotify_event, name) + d->buffer.ev.len; + if (d->buffer_filled < sz) + return -EIO; + + if (d->buffer.ev.mask & IN_Q_OVERFLOW) { + struct inode_data *inode_data; + + /* The queue overran, let's pass this event to all event sources connected to this inotify + * object */ + + HASHMAP_FOREACH(inode_data, d->inodes) { + sd_event_source *s; + + LIST_FOREACH(inotify.by_inode_data, s, inode_data->event_sources) { + + if (event_source_is_offline(s)) + continue; + + r = source_set_pending(s, true); + if (r < 0) + return r; + } + } + } else { + struct inode_data *inode_data; + sd_event_source *s; + + /* Find the inode object for this watch descriptor. If IN_IGNORED is set we also remove it from + * our watch descriptor table. */ + if (d->buffer.ev.mask & IN_IGNORED) { + + inode_data = hashmap_remove(d->wd, INT_TO_PTR(d->buffer.ev.wd)); + if (!inode_data) { + event_inotify_data_drop(e, d, sz); + continue; + } + + /* The watch descriptor was removed by the kernel, let's drop it here too */ + inode_data->wd = -1; + } else { + inode_data = hashmap_get(d->wd, INT_TO_PTR(d->buffer.ev.wd)); + if (!inode_data) { + event_inotify_data_drop(e, d, sz); + continue; + } + } + + /* Trigger all event sources that are interested in these events. Also trigger all event + * sources if IN_IGNORED or IN_UNMOUNT is set. */ + LIST_FOREACH(inotify.by_inode_data, s, inode_data->event_sources) { + + if (event_source_is_offline(s)) + continue; + + if ((d->buffer.ev.mask & (IN_IGNORED|IN_UNMOUNT)) == 0 && + (s->inotify.mask & d->buffer.ev.mask & IN_ALL_EVENTS) == 0) + continue; + + r = source_set_pending(s, true); + if (r < 0) + return r; + } + } + + /* Something pending now? If so, let's finish, otherwise let's read more. */ + if (d->n_pending > 0) + return 1; + } + + return 0; +} + +static int process_inotify(sd_event *e) { + struct inotify_data *d; + int r, done = 0; + + assert(e); + + LIST_FOREACH(buffered, d, e->inotify_data_buffered) { + r = event_inotify_data_process(e, d); + if (r < 0) + return r; + if (r > 0) + done ++; + } + + return done; +} + +static int source_dispatch(sd_event_source *s) { + _cleanup_(sd_event_unrefp) sd_event *saved_event = NULL; + EventSourceType saved_type; + int r = 0; + + assert(s); + assert(s->pending || s->type == SOURCE_EXIT); + + /* Save the event source type, here, so that we still know it after the event callback which might + * invalidate the event. */ + saved_type = s->type; + + /* Similarly, store a reference to the event loop object, so that we can still access it after the + * callback might have invalidated/disconnected the event source. */ + saved_event = sd_event_ref(s->event); + + /* Check if we hit the ratelimit for this event source, and if so, let's disable it. */ + assert(!s->ratelimited); + if (!ratelimit_below(&s->rate_limit)) { + r = event_source_enter_ratelimited(s); + if (r < 0) + return r; + + return 1; + } + + if (!IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) { + r = source_set_pending(s, false); + if (r < 0) + return r; + } + + if (s->type != SOURCE_POST) { + sd_event_source *z; + + /* If we execute a non-post source, let's mark all post sources as pending. */ + + SET_FOREACH(z, s->event->post_sources) { + if (event_source_is_offline(z)) + continue; + + r = source_set_pending(z, true); + if (r < 0) + return r; + } + } + + if (s->enabled == SD_EVENT_ONESHOT) { + r = sd_event_source_set_enabled(s, SD_EVENT_OFF); + if (r < 0) + return r; + } + + s->dispatching = true; + + switch (s->type) { + + case SOURCE_IO: + r = s->io.callback(s, s->io.fd, s->io.revents, s->userdata); + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: + r = s->time.callback(s, s->time.next, s->userdata); + break; + + case SOURCE_SIGNAL: + r = s->signal.callback(s, &s->signal.siginfo, s->userdata); + break; + + case SOURCE_CHILD: { + bool zombie; + + zombie = IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED); + + r = s->child.callback(s, &s->child.siginfo, s->userdata); + + /* Now, reap the PID for good. */ + if (zombie) { + (void) waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|WEXITED); + s->child.waited = true; + } + + break; + } + + case SOURCE_DEFER: + r = s->defer.callback(s, s->userdata); + break; + + case SOURCE_POST: + r = s->post.callback(s, s->userdata); + break; + + case SOURCE_EXIT: + r = s->exit.callback(s, s->userdata); + break; + + case SOURCE_INOTIFY: { + struct sd_event *e = s->event; + struct inotify_data *d; + size_t sz; + + assert(s->inotify.inode_data); + assert_se(d = s->inotify.inode_data->inotify_data); + + assert(d->buffer_filled >= offsetof(struct inotify_event, name)); + sz = offsetof(struct inotify_event, name) + d->buffer.ev.len; + assert(d->buffer_filled >= sz); + + r = s->inotify.callback(s, &d->buffer.ev, s->userdata); + + /* When no event is pending anymore on this inotify object, then let's drop the event from the + * buffer. */ + if (d->n_pending == 0) + event_inotify_data_drop(e, d, sz); + + break; + } + + case SOURCE_WATCHDOG: + case _SOURCE_EVENT_SOURCE_TYPE_MAX: + case _SOURCE_EVENT_SOURCE_TYPE_INVALID: + assert_not_reached("Wut? I shouldn't exist."); + } + + s->dispatching = false; + + if (r < 0) { + log_debug_errno(r, "Event source %s (type %s) returned error, %s: %m", + strna(s->description), + event_source_type_to_string(saved_type), + s->exit_on_failure ? "exiting" : "disabling"); + + if (s->exit_on_failure) + (void) sd_event_exit(saved_event, r); + } + + if (s->n_ref == 0) + source_free(s); + else if (r < 0) + sd_event_source_set_enabled(s, SD_EVENT_OFF); + + return 1; +} + +static int event_prepare(sd_event *e) { + int r; + + assert(e); + + for (;;) { + sd_event_source *s; + + s = prioq_peek(e->prepare); + if (!s || s->prepare_iteration == e->iteration || event_source_is_offline(s)) + break; + + s->prepare_iteration = e->iteration; + r = prioq_reshuffle(e->prepare, s, &s->prepare_index); + if (r < 0) + return r; + + assert(s->prepare); + + s->dispatching = true; + r = s->prepare(s, s->userdata); + s->dispatching = false; + + if (r < 0) { + log_debug_errno(r, "Prepare callback of event source %s (type %s) returned error, %s: %m", + strna(s->description), + event_source_type_to_string(s->type), + s->exit_on_failure ? "exiting" : "disabling"); + + if (s->exit_on_failure) + (void) sd_event_exit(e, r); + } + + if (s->n_ref == 0) + source_free(s); + else if (r < 0) + sd_event_source_set_enabled(s, SD_EVENT_OFF); + } + + return 0; +} + +static int dispatch_exit(sd_event *e) { + sd_event_source *p; + int r; + + assert(e); + + p = prioq_peek(e->exit); + if (!p || event_source_is_offline(p)) { + e->state = SD_EVENT_FINISHED; + return 0; + } + + _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e); + e->iteration++; + e->state = SD_EVENT_EXITING; + r = source_dispatch(p); + e->state = SD_EVENT_INITIAL; + return r; +} + +static sd_event_source* event_next_pending(sd_event *e) { + sd_event_source *p; + + assert(e); + + p = prioq_peek(e->pending); + if (!p) + return NULL; + + if (event_source_is_offline(p)) + return NULL; + + return p; +} + +static int arm_watchdog(sd_event *e) { + struct itimerspec its = {}; + usec_t t; + + assert(e); + assert(e->watchdog_fd >= 0); + + t = sleep_between(e, + e->watchdog_last + (e->watchdog_period / 2), + e->watchdog_last + (e->watchdog_period * 3 / 4)); + + timespec_store(&its.it_value, t); + + /* Make sure we never set the watchdog to 0, which tells the + * kernel to disable it. */ + if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0) + its.it_value.tv_nsec = 1; + + if (timerfd_settime(e->watchdog_fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) + return -errno; + + return 0; +} + +static int process_watchdog(sd_event *e) { + assert(e); + + if (!e->watchdog) + return 0; + + /* Don't notify watchdog too often */ + if (e->watchdog_last + e->watchdog_period / 4 > e->timestamp.monotonic) + return 0; + + sd_notify(false, "WATCHDOG=1"); + e->watchdog_last = e->timestamp.monotonic; + + return arm_watchdog(e); +} + +static void event_close_inode_data_fds(sd_event *e) { + struct inode_data *d; + + assert(e); + + /* Close the fds pointing to the inodes to watch now. We need to close them as they might otherwise pin + * filesystems. But we can't close them right-away as we need them as long as the user still wants to make + * adjustments to the even source, such as changing the priority (which requires us to remove and re-add a watch + * for the inode). Hence, let's close them when entering the first iteration after they were added, as a + * compromise. */ + + while ((d = e->inode_data_to_close)) { + assert(d->fd >= 0); + d->fd = safe_close(d->fd); + + LIST_REMOVE(to_close, e->inode_data_to_close, d); + } +} + +_public_ int sd_event_prepare(sd_event *e) { + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + + /* Let's check that if we are a default event loop we are executed in the correct thread. We only do + * this check here once, since gettid() is typically not cached, and thus want to minimize + * syscalls */ + assert_return(!e->default_event_ptr || e->tid == gettid(), -EREMOTEIO); + + /* Make sure that none of the preparation callbacks ends up freeing the event source under our feet */ + _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e); + + if (e->exit_requested) + goto pending; + + e->iteration++; + + e->state = SD_EVENT_PREPARING; + r = event_prepare(e); + e->state = SD_EVENT_INITIAL; + if (r < 0) + return r; + + r = event_arm_timer(e, &e->realtime); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->boottime); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->monotonic); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->realtime_alarm); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->boottime_alarm); + if (r < 0) + return r; + + event_close_inode_data_fds(e); + + if (event_next_pending(e) || e->need_process_child) + goto pending; + + e->state = SD_EVENT_ARMED; + + return 0; + +pending: + e->state = SD_EVENT_ARMED; + r = sd_event_wait(e, 0); + if (r == 0) + e->state = SD_EVENT_ARMED; + + return r; +} + +static int epoll_wait_usec( + int fd, + struct epoll_event *events, + int maxevents, + usec_t timeout) { + + int r, msec; +#if 0 + static bool epoll_pwait2_absent = false; + + /* A wrapper that uses epoll_pwait2() if available, and falls back to epoll_wait() if not. + * + * FIXME: this is temporarily disabled until epoll_pwait2() becomes more widely available. + * See https://github.com/systemd/systemd/pull/18973 and + * https://github.com/systemd/systemd/issues/19052. */ + + if (!epoll_pwait2_absent && timeout != USEC_INFINITY) { + struct timespec ts; + + r = epoll_pwait2(fd, + events, + maxevents, + timespec_store(&ts, timeout), + NULL); + if (r >= 0) + return r; + if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) + return -errno; /* Only fallback to old epoll_wait() if the syscall is masked or not + * supported. */ + + epoll_pwait2_absent = true; + } +#endif + + if (timeout == USEC_INFINITY) + msec = -1; + else { + usec_t k; + + k = DIV_ROUND_UP(timeout, USEC_PER_MSEC); + if (k >= INT_MAX) + msec = INT_MAX; /* Saturate */ + else + msec = (int) k; + } + + r = epoll_wait(fd, + events, + maxevents, + msec); + if (r < 0) + return -errno; + + return r; +} + +static int process_epoll(sd_event *e, usec_t timeout, int64_t threshold, int64_t *ret_min_priority) { + size_t n_event_queue, m, n_event_max; + int64_t min_priority = threshold; + bool something_new = false; + int r; + + assert(e); + assert(ret_min_priority); + + n_event_queue = MAX(e->n_sources, 1u); + if (!GREEDY_REALLOC(e->event_queue, n_event_queue)) + return -ENOMEM; + + n_event_max = MALLOC_ELEMENTSOF(e->event_queue); + + /* If we still have inotify data buffered, then query the other fds, but don't wait on it */ + if (e->inotify_data_buffered) + timeout = 0; + + for (;;) { + r = epoll_wait_usec( + e->epoll_fd, + e->event_queue, + n_event_max, + timeout); + if (r < 0) + return r; + + m = (size_t) r; + + if (m < n_event_max) + break; + + if (n_event_max >= n_event_queue * 10) + break; + + if (!GREEDY_REALLOC(e->event_queue, n_event_max + n_event_queue)) + return -ENOMEM; + + n_event_max = MALLOC_ELEMENTSOF(e->event_queue); + timeout = 0; + } + + /* Set timestamp only when this is called first time. */ + if (threshold == INT64_MAX) + triple_timestamp_get(&e->timestamp); + + for (size_t i = 0; i < m; i++) { + + if (e->event_queue[i].data.ptr == INT_TO_PTR(SOURCE_WATCHDOG)) + r = flush_timer(e, e->watchdog_fd, e->event_queue[i].events, NULL); + else { + WakeupType *t = e->event_queue[i].data.ptr; + + switch (*t) { + + case WAKEUP_EVENT_SOURCE: { + sd_event_source *s = e->event_queue[i].data.ptr; + + assert(s); + + if (s->priority > threshold) + continue; + + min_priority = MIN(min_priority, s->priority); + + switch (s->type) { + + case SOURCE_IO: + r = process_io(e, s, e->event_queue[i].events); + break; + + case SOURCE_CHILD: + r = process_pidfd(e, s, e->event_queue[i].events); + break; + + default: + assert_not_reached("Unexpected event source type"); + } + + break; + } + + case WAKEUP_CLOCK_DATA: { + struct clock_data *d = e->event_queue[i].data.ptr; + + assert(d); + + r = flush_timer(e, d->fd, e->event_queue[i].events, &d->next); + break; + } + + case WAKEUP_SIGNAL_DATA: + r = process_signal(e, e->event_queue[i].data.ptr, e->event_queue[i].events, &min_priority); + break; + + case WAKEUP_INOTIFY_DATA: + r = event_inotify_data_read(e, e->event_queue[i].data.ptr, e->event_queue[i].events, threshold); + break; + + default: + assert_not_reached("Invalid wake-up pointer"); + } + } + if (r < 0) + return r; + if (r > 0) + something_new = true; + } + + *ret_min_priority = min_priority; + return something_new; +} + +_public_ int sd_event_wait(sd_event *e, uint64_t timeout) { + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_ARMED, -EBUSY); + + if (e->exit_requested) { + e->state = SD_EVENT_PENDING; + return 1; + } + + for (int64_t threshold = INT64_MAX; ; threshold--) { + int64_t epoll_min_priority, child_min_priority; + + /* There may be a possibility that new epoll (especially IO) and child events are + * triggered just after process_epoll() call but before process_child(), and the new IO + * events may have higher priority than the child events. To salvage these events, + * let's call epoll_wait() again, but accepts only events with higher priority than the + * previous. See issue https://github.com/systemd/systemd/issues/18190 and comments + * https://github.com/systemd/systemd/pull/18750#issuecomment-785801085 + * https://github.com/systemd/systemd/pull/18922#issuecomment-792825226 */ + + r = process_epoll(e, timeout, threshold, &epoll_min_priority); + if (r == -EINTR) { + e->state = SD_EVENT_PENDING; + return 1; + } + if (r < 0) + goto finish; + if (r == 0 && threshold < INT64_MAX) + /* No new epoll event. */ + break; + + r = process_child(e, threshold, &child_min_priority); + if (r < 0) + goto finish; + if (r == 0) + /* No new child event. */ + break; + + threshold = MIN(epoll_min_priority, child_min_priority); + if (threshold == INT64_MIN) + break; + + timeout = 0; + } + + r = process_watchdog(e); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.realtime, &e->realtime); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.boottime, &e->boottime); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.monotonic, &e->monotonic); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.realtime, &e->realtime_alarm); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.boottime, &e->boottime_alarm); + if (r < 0) + goto finish; + + r = process_inotify(e); + if (r < 0) + goto finish; + + if (event_next_pending(e)) { + e->state = SD_EVENT_PENDING; + return 1; + } + + r = 0; + +finish: + e->state = SD_EVENT_INITIAL; + + return r; +} + +_public_ int sd_event_dispatch(sd_event *e) { + sd_event_source *p; + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_PENDING, -EBUSY); + + if (e->exit_requested) + return dispatch_exit(e); + + p = event_next_pending(e); + if (p) { + _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e); + + e->state = SD_EVENT_RUNNING; + r = source_dispatch(p); + e->state = SD_EVENT_INITIAL; + return r; + } + + e->state = SD_EVENT_INITIAL; + + return 1; +} + +static void event_log_delays(sd_event *e) { + char b[ELEMENTSOF(e->delays) * DECIMAL_STR_MAX(unsigned) + 1], *p; + size_t l, i; + + p = b; + l = sizeof(b); + for (i = 0; i < ELEMENTSOF(e->delays); i++) { + l = strpcpyf(&p, l, "%u ", e->delays[i]); + e->delays[i] = 0; + } + log_debug("Event loop iterations: %s", b); +} + +_public_ int sd_event_run(sd_event *e, uint64_t timeout) { + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + + if (e->profile_delays && e->last_run_usec != 0) { + usec_t this_run; + unsigned l; + + this_run = now(CLOCK_MONOTONIC); + + l = u64log2(this_run - e->last_run_usec); + assert(l < ELEMENTSOF(e->delays)); + e->delays[l]++; + + if (this_run - e->last_log_usec >= 5*USEC_PER_SEC) { + event_log_delays(e); + e->last_log_usec = this_run; + } + } + + /* Make sure that none of the preparation callbacks ends up freeing the event source under our feet */ + _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e); + + r = sd_event_prepare(e); + if (r == 0) + /* There was nothing? Then wait... */ + r = sd_event_wait(e, timeout); + + if (e->profile_delays) + e->last_run_usec = now(CLOCK_MONOTONIC); + + if (r > 0) { + /* There's something now, then let's dispatch it */ + r = sd_event_dispatch(e); + if (r < 0) + return r; + + return 1; + } + + return r; +} + +_public_ int sd_event_loop(sd_event *e) { + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + + _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = NULL; + + while (e->state != SD_EVENT_FINISHED) { + r = sd_event_run(e, UINT64_MAX); + if (r < 0) + return r; + } + + return e->exit_code; +} + +_public_ int sd_event_get_fd(sd_event *e) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + + return e->epoll_fd; +} + +_public_ int sd_event_get_state(sd_event *e) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + + return e->state; +} + +_public_ int sd_event_get_exit_code(sd_event *e, int *code) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(code, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!e->exit_requested) + return -ENODATA; + + *code = e->exit_code; + return 0; +} + +_public_ int sd_event_exit(sd_event *e, int code) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + e->exit_requested = true; + e->exit_code = code; + + return 0; +} + +_public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(usec, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!TRIPLE_TIMESTAMP_HAS_CLOCK(clock)) + return -EOPNOTSUPP; + + /* Generate a clean error in case CLOCK_BOOTTIME is not available. Note that don't use clock_supported() here, + * for a reason: there are systems where CLOCK_BOOTTIME is supported, but CLOCK_BOOTTIME_ALARM is not, but for + * the purpose of getting the time this doesn't matter. */ + if (IN_SET(clock, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) && !clock_boottime_supported()) + return -EOPNOTSUPP; + + if (!triple_timestamp_is_set(&e->timestamp)) { + /* Implicitly fall back to now() if we never ran before and thus have no cached time. */ + *usec = now(clock); + return 1; + } + + *usec = triple_timestamp_by_clock(&e->timestamp, clock); + return 0; +} + +_public_ int sd_event_default(sd_event **ret) { + sd_event *e = NULL; + int r; + + if (!ret) + return !!default_event; + + if (default_event) { + *ret = sd_event_ref(default_event); + return 0; + } + + r = sd_event_new(&e); + if (r < 0) + return r; + + e->default_event_ptr = &default_event; + e->tid = gettid(); + default_event = e; + + *ret = e; + return 1; +} + +#if 0 /* NM_IGNORED */ +_public_ int sd_event_get_tid(sd_event *e, pid_t *tid) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(tid, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + if (e->tid != 0) { + *tid = e->tid; + return 0; + } + + return -ENXIO; +} + +_public_ int sd_event_set_watchdog(sd_event *e, int b) { + int r; + + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + + if (e->watchdog == !!b) + return e->watchdog; + + if (b) { + r = sd_watchdog_enabled(false, &e->watchdog_period); + if (r <= 0) + return r; + + /* Issue first ping immediately */ + sd_notify(false, "WATCHDOG=1"); + e->watchdog_last = now(CLOCK_MONOTONIC); + + e->watchdog_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC); + if (e->watchdog_fd < 0) + return -errno; + + r = arm_watchdog(e); + if (r < 0) + goto fail; + + struct epoll_event ev = { + .events = EPOLLIN, + .data.ptr = INT_TO_PTR(SOURCE_WATCHDOG), + }; + + if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, e->watchdog_fd, &ev) < 0) { + r = -errno; + goto fail; + } + + } else { + if (e->watchdog_fd >= 0) { + (void) epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, e->watchdog_fd, NULL); + e->watchdog_fd = safe_close(e->watchdog_fd); + } + } + + e->watchdog = !!b; + return e->watchdog; + +fail: + e->watchdog_fd = safe_close(e->watchdog_fd); + return r; +} + +_public_ int sd_event_get_watchdog(sd_event *e) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + + return e->watchdog; +} + +_public_ int sd_event_get_iteration(sd_event *e, uint64_t *ret) { + assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(!event_pid_changed(e), -ECHILD); + + *ret = e->iteration; + return 0; +} + +_public_ int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback) { + assert_return(s, -EINVAL); + + s->destroy_callback = callback; + return 0; +} + +_public_ int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret) { + assert_return(s, -EINVAL); + + if (ret) + *ret = s->destroy_callback; + + return !!s->destroy_callback; +} + +_public_ int sd_event_source_get_floating(sd_event_source *s) { + assert_return(s, -EINVAL); + + return s->floating; +} + +_public_ int sd_event_source_set_floating(sd_event_source *s, int b) { + assert_return(s, -EINVAL); + + if (s->floating == !!b) + return 0; + + if (!s->event) /* Already disconnected */ + return -ESTALE; + + s->floating = b; + + if (b) { + sd_event_source_ref(s); + sd_event_unref(s->event); + } else { + sd_event_ref(s->event); + sd_event_source_unref(s); + } + + return 1; +} + +_public_ int sd_event_source_get_exit_on_failure(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type != SOURCE_EXIT, -EDOM); + + return s->exit_on_failure; +} + +_public_ int sd_event_source_set_exit_on_failure(sd_event_source *s, int b) { + assert_return(s, -EINVAL); + assert_return(s->type != SOURCE_EXIT, -EDOM); + + if (s->exit_on_failure == !!b) + return 0; + + s->exit_on_failure = b; + return 1; +} + +_public_ int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval, unsigned burst) { + int r; + + assert_return(s, -EINVAL); + + /* Turning on ratelimiting on event source types that don't support it, is a loggable offense. Doing + * so is a programming error. */ + assert_return(EVENT_SOURCE_CAN_RATE_LIMIT(s->type), -EDOM); + + /* When ratelimiting is configured we'll always reset the rate limit state first and start fresh, + * non-ratelimited. */ + r = event_source_leave_ratelimit(s); + if (r < 0) + return r; + + s->rate_limit = (RateLimit) { interval, burst }; + return 0; +} + +_public_ int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval, unsigned *ret_burst) { + assert_return(s, -EINVAL); + + /* Querying whether an event source has ratelimiting configured is not a loggable offsense, hence + * don't use assert_return(). Unlike turning on ratelimiting it's not really a programming error */ + if (!EVENT_SOURCE_CAN_RATE_LIMIT(s->type)) + return -EDOM; + + if (!ratelimit_configured(&s->rate_limit)) + return -ENOEXEC; + + if (ret_interval) + *ret_interval = s->rate_limit.interval; + if (ret_burst) + *ret_burst = s->rate_limit.burst; + + return 0; +} + +_public_ int sd_event_source_is_ratelimited(sd_event_source *s) { + assert_return(s, -EINVAL); + + if (!EVENT_SOURCE_CAN_RATE_LIMIT(s->type)) + return false; + + if (!ratelimit_configured(&s->rate_limit)) + return false; + + return s->ratelimited; +} +#endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c new file mode 100644 index 00000000..3a2bffab --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c @@ -0,0 +1,240 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include "fd-util.h" +#include "fs-util.h" +#include "hexdecoct.h" +#include "id128-util.h" +#include "io-util.h" +#include "stdio-util.h" +#include "string-util.h" + +#if 0 /* NM_IGNORED */ +char *id128_to_uuid_string(sd_id128_t id, char s[static ID128_UUID_STRING_MAX]) { + unsigned n, k = 0; + + assert(s); + + /* Similar to sd_id128_to_string() but formats the result as UUID instead of plain hex chars */ + + for (n = 0; n < 16; n++) { + + if (IN_SET(n, 4, 6, 8, 10)) + s[k++] = '-'; + + s[k++] = hexchar(id.bytes[n] >> 4); + s[k++] = hexchar(id.bytes[n] & 0xF); + } + + assert(k == 36); + + s[k] = 0; + + return s; +} + +bool id128_is_valid(const char *s) { + size_t i, l; + + assert(s); + + l = strlen(s); + if (l == 32) { + + /* Plain formatted 128bit hex string */ + + for (i = 0; i < l; i++) { + char c = s[i]; + + if (!(c >= '0' && c <= '9') && + !(c >= 'a' && c <= 'z') && + !(c >= 'A' && c <= 'Z')) + return false; + } + + } else if (l == 36) { + + /* Formatted UUID */ + + for (i = 0; i < l; i++) { + char c = s[i]; + + if (IN_SET(i, 8, 13, 18, 23)) { + if (c != '-') + return false; + } else { + if (!(c >= '0' && c <= '9') && + !(c >= 'a' && c <= 'z') && + !(c >= 'A' && c <= 'Z')) + return false; + } + } + + } else + return false; + + return true; +} +#endif /* NM_IGNORED */ + +int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret) { + char buffer[36 + 2]; + ssize_t l; + + assert(fd >= 0); + assert(f < _ID128_FORMAT_MAX); + + /* Reads an 128bit ID from a file, which may either be in plain format (32 hex digits), or in UUID format, both + * optionally followed by a newline and nothing else. ID files should really be newline terminated, but if they + * aren't that's OK too, following the rule of "Be conservative in what you send, be liberal in what you + * accept". */ + + l = loop_read(fd, buffer, sizeof(buffer), false); /* we expect a short read of either 32/33 or 36/37 chars */ + if (l < 0) + return (int) l; + if (l == 0) /* empty? */ + return -ENOMEDIUM; + + switch (l) { + + case 13: + case 14: + /* Treat an "uninitialized" id file like an empty one */ + return f == ID128_PLAIN_OR_UNINIT && strneq(buffer, "uninitialized\n", l) ? -ENOMEDIUM : -EINVAL; + + case 33: /* plain UUID with trailing newline */ + if (buffer[32] != '\n') + return -EINVAL; + + _fallthrough_; + case 32: /* plain UUID without trailing newline */ + if (f == ID128_UUID) + return -EINVAL; + + buffer[32] = 0; + break; + + case 37: /* RFC UUID with trailing newline */ + if (buffer[36] != '\n') + return -EINVAL; + + _fallthrough_; + case 36: /* RFC UUID without trailing newline */ + if (IN_SET(f, ID128_PLAIN, ID128_PLAIN_OR_UNINIT)) + return -EINVAL; + + buffer[36] = 0; + break; + + default: + return -EINVAL; + } + + return sd_id128_from_string(buffer, ret); +} + +int id128_read(const char *p, Id128Format f, sd_id128_t *ret) { + _cleanup_close_ int fd = -1; + + fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return -errno; + + return id128_read_fd(fd, f, ret); +} + +#if 0 /* NM_IGNORED */ +int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync) { + char buffer[36 + 2]; + size_t sz; + int r; + + assert(fd >= 0); + assert(f < _ID128_FORMAT_MAX); + + if (f != ID128_UUID) { + sd_id128_to_string(id, buffer); + buffer[32] = '\n'; + sz = 33; + } else { + id128_to_uuid_string(id, buffer); + buffer[36] = '\n'; + sz = 37; + } + + r = loop_write(fd, buffer, sz, false); + if (r < 0) + return r; + + if (do_sync) { + if (fsync(fd) < 0) + return -errno; + + r = fsync_directory_of_file(fd); + if (r < 0) + return r; + } + + return 0; +} + +int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync) { + _cleanup_close_ int fd = -1; + + fd = open(p, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, 0444); + if (fd < 0) + return -errno; + + return id128_write_fd(fd, f, id, do_sync); +} + +void id128_hash_func(const sd_id128_t *p, struct siphash *state) { + siphash24_compress(p, sizeof(sd_id128_t), state); +} + +int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) { + return memcmp(a, b, 16); +} + +sd_id128_t id128_make_v4_uuid(sd_id128_t id) { + /* Stolen from generate_random_uuid() of drivers/char/random.c + * in the kernel sources */ + + /* Set UUID version to 4 --- truly random generation */ + id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40; + + /* Set the UUID variant to DCE */ + id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80; + + return id; +} + +DEFINE_HASH_OPS(id128_hash_ops, sd_id128_t, id128_hash_func, id128_compare_func); + +int id128_get_product(sd_id128_t *ret) { + sd_id128_t uuid; + int r; + + assert(ret); + + /* Reads the systems product UUID from DMI or devicetree (where it is located on POWER). This is + * particularly relevant in VM environments, where VM managers typically place a VM uuid there. */ + + r = id128_read("/sys/class/dmi/id/product_uuid", ID128_UUID, &uuid); + if (r == -ENOENT) + r = id128_read("/sys/firmware/devicetree/base/vm,uuid", ID128_UUID, &uuid); + if (r < 0) + return r; + + if (sd_id128_is_null(uuid) || sd_id128_is_allf(uuid)) + return -EADDRNOTAVAIL; /* Recognizable error */ + + *ret = uuid; + return 0; +} +#endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h new file mode 100644 index 00000000..053ef0a6 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <stdbool.h> + +#include "sd-id128.h" + +#include "hash-funcs.h" +#include "macro.h" + +#define ID128_UUID_STRING_MAX 37 + +char *id128_to_uuid_string(sd_id128_t id, char s[static ID128_UUID_STRING_MAX]); + +bool id128_is_valid(const char *s) _pure_; + +typedef enum Id128Format { + ID128_ANY, + ID128_PLAIN, /* formatted as 32 hex chars as-is */ + ID128_PLAIN_OR_UNINIT, /* formatted as 32 hex chars as-is; allow special "uninitialized" + * value when reading from file (id128_read() and id128_read_fd()). + * + * This format should be used when reading a machine-id file. */ + ID128_UUID, /* formatted as 36 character uuid string */ + _ID128_FORMAT_MAX, +} Id128Format; + +int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret); +int id128_read(const char *p, Id128Format f, sd_id128_t *ret); + +int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync); +int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync); + +void id128_hash_func(const sd_id128_t *p, struct siphash *state); +int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) _pure_; +extern const struct hash_ops id128_hash_ops; + +sd_id128_t id128_make_v4_uuid(sd_id128_t id); + +int id128_get_product(sd_id128_t *ret); diff --git a/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c b/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c new file mode 100644 index 00000000..64b8c787 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-core.h" + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include "sd-id128.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "hexdecoct.h" +#include "id128-util.h" +#include "io-util.h" +#include "khash.h" +#include "macro.h" +#include "missing_syscall.h" +#include "random-util.h" +#include "user-util.h" +#include "util.h" + +#if 0 /* NM_IGNORED */ +_public_ char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]) { + unsigned n; + + assert_return(s, NULL); + + for (n = 0; n < 16; n++) { + s[n*2] = hexchar(id.bytes[n] >> 4); + s[n*2+1] = hexchar(id.bytes[n] & 0xF); + } + + s[32] = 0; + + return s; +} +#endif /* NM_IGNORED */ + +_public_ int sd_id128_from_string(const char s[], sd_id128_t *ret) { + unsigned n, i; + sd_id128_t t; + bool is_guid = false; + + assert_return(s, -EINVAL); + + for (n = 0, i = 0; n < 16;) { + int a, b; + + if (s[i] == '-') { + /* Is this a GUID? Then be nice, and skip over + * the dashes */ + + if (i == 8) + is_guid = true; + else if (IN_SET(i, 13, 18, 23)) { + if (!is_guid) + return -EINVAL; + } else + return -EINVAL; + + i++; + continue; + } + + a = unhexchar(s[i++]); + if (a < 0) + return -EINVAL; + + b = unhexchar(s[i++]); + if (b < 0) + return -EINVAL; + + t.bytes[n++] = (a << 4) | b; + } + + if (i != (is_guid ? 36 : 32)) + return -EINVAL; + + if (s[i] != 0) + return -EINVAL; + + if (ret) + *ret = t; + return 0; +} + +_public_ int sd_id128_get_machine(sd_id128_t *ret) { + static thread_local sd_id128_t saved_machine_id = {}; + int r; + + assert_return(ret, -EINVAL); + + if (sd_id128_is_null(saved_machine_id)) { + r = id128_read("/etc/machine-id", ID128_PLAIN, &saved_machine_id); + if (r < 0) + return r; + + if (sd_id128_is_null(saved_machine_id)) + return -ENOMEDIUM; + } + + *ret = saved_machine_id; + return 0; +} + +_public_ int sd_id128_get_boot(sd_id128_t *ret) { + static thread_local sd_id128_t saved_boot_id = {}; + int r; + + assert_return(ret, -EINVAL); + + if (sd_id128_is_null(saved_boot_id)) { + r = id128_read("/proc/sys/kernel/random/boot_id", ID128_UUID, &saved_boot_id); + if (r < 0) + return r; + } + + *ret = saved_boot_id; + return 0; +} + +#if 0 /* NM_IGNORED */ +static int get_invocation_from_keyring(sd_id128_t *ret) { + _cleanup_free_ char *description = NULL; + char *d, *p, *g, *u, *e; + unsigned long perms; + key_serial_t key; + size_t sz = 256; + uid_t uid; + gid_t gid; + int r, c; + +#define MAX_PERMS ((unsigned long) (KEY_POS_VIEW|KEY_POS_READ|KEY_POS_SEARCH| \ + KEY_USR_VIEW|KEY_USR_READ|KEY_USR_SEARCH)) + + assert(ret); + + key = request_key("user", "invocation_id", NULL, 0); + if (key == -1) { + /* Keyring support not available? No invocation key stored? */ + if (IN_SET(errno, ENOSYS, ENOKEY)) + return -ENXIO; + + return -errno; + } + + for (;;) { + description = new(char, sz); + if (!description) + return -ENOMEM; + + c = keyctl(KEYCTL_DESCRIBE, key, (unsigned long) description, sz, 0); + if (c < 0) + return -errno; + + if ((size_t) c <= sz) + break; + + sz = c; + free(description); + } + + /* The kernel returns a final NUL in the string, verify that. */ + assert(description[c-1] == 0); + + /* Chop off the final description string */ + d = strrchr(description, ';'); + if (!d) + return -EIO; + *d = 0; + + /* Look for the permissions */ + p = strrchr(description, ';'); + if (!p) + return -EIO; + + errno = 0; + perms = strtoul(p + 1, &e, 16); + if (errno > 0) + return -errno; + if (e == p + 1) /* Read at least one character */ + return -EIO; + if (e != d) /* Must reached the end */ + return -EIO; + + if ((perms & ~MAX_PERMS) != 0) + return -EPERM; + + *p = 0; + + /* Look for the group ID */ + g = strrchr(description, ';'); + if (!g) + return -EIO; + r = parse_gid(g + 1, &gid); + if (r < 0) + return r; + if (gid != 0) + return -EPERM; + *g = 0; + + /* Look for the user ID */ + u = strrchr(description, ';'); + if (!u) + return -EIO; + r = parse_uid(u + 1, &uid); + if (r < 0) + return r; + if (uid != 0) + return -EPERM; + + c = keyctl(KEYCTL_READ, key, (unsigned long) ret, sizeof(sd_id128_t), 0); + if (c < 0) + return -errno; + if (c != sizeof(sd_id128_t)) + return -EIO; + + return 0; +} + +static int get_invocation_from_environment(sd_id128_t *ret) { + const char *e; + + assert(ret); + + e = secure_getenv("INVOCATION_ID"); + if (!e) + return -ENXIO; + + return sd_id128_from_string(e, ret); +} + +_public_ int sd_id128_get_invocation(sd_id128_t *ret) { + static thread_local sd_id128_t saved_invocation_id = {}; + int r; + + assert_return(ret, -EINVAL); + + if (sd_id128_is_null(saved_invocation_id)) { + /* We first check the environment. The environment variable is primarily relevant for user + * services, and sufficiently safe as long as no privilege boundary is involved. */ + r = get_invocation_from_environment(&saved_invocation_id); + if (r < 0 && r != -ENXIO) + return r; + + /* The kernel keyring is relevant for system services (as for user services we don't store + * the invocation ID in the keyring, as there'd be no trust benefit in that). */ + r = get_invocation_from_keyring(&saved_invocation_id); + if (r < 0) + return r; + } + + *ret = saved_invocation_id; + return 0; +} + +_public_ int sd_id128_randomize(sd_id128_t *ret) { + sd_id128_t t; + int r; + + assert_return(ret, -EINVAL); + + /* We allow usage if x86-64 RDRAND here. It might not be trusted enough for keeping secrets, but it should be + * fine for UUIDS. */ + r = genuine_random_bytes(&t, sizeof t, RANDOM_ALLOW_RDRAND); + if (r < 0) + return r; + + /* Turn this into a valid v4 UUID, to be nice. Note that we + * only guarantee this for newly generated UUIDs, not for + * pre-existing ones. */ + + *ret = id128_make_v4_uuid(t); + return 0; +} + +static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret) { + _cleanup_(khash_unrefp) khash *h = NULL; + sd_id128_t result; + const void *p; + int r; + + assert(ret); + + r = khash_new_with_key(&h, "hmac(sha256)", &base, sizeof(base)); + if (r < 0) + return r; + + r = khash_put(h, &app_id, sizeof(app_id)); + if (r < 0) + return r; + + r = khash_digest_data(h, &p); + if (r < 0) + return r; + + /* We chop off the trailing 16 bytes */ + memcpy(&result, p, MIN(khash_get_size(h), sizeof(result))); + + *ret = id128_make_v4_uuid(result); + return 0; +} + +_public_ int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret) { + sd_id128_t id; + int r; + + assert_return(ret, -EINVAL); + + r = sd_id128_get_machine(&id); + if (r < 0) + return r; + + return get_app_specific(id, app_id, ret); +} + +_public_ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret) { + sd_id128_t id; + int r; + + assert_return(ret, -EINVAL); + + r = sd_id128_get_boot(&id); + if (r < 0) + return r; + + return get_app_specific(id, app_id, ret); +} +#endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-core/src/systemd/_sd-common.h b/src/libnm-systemd-core/src/systemd/_sd-common.h new file mode 100644 index 00000000..e1214296 --- /dev/null +++ b/src/libnm-systemd-core/src/systemd/_sd-common.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdcommonhfoo +#define foosdcommonhfoo + +/*** + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* This is a private header; never even think of including this directly! */ + +#if defined(__INCLUDE_LEVEL__) && __INCLUDE_LEVEL__ <= 1 && !defined(__COVERITY__) +# error "Do not include _sd-common.h directly; it is a private header." +#endif + +typedef void (*_sd_destroy_t)(void *userdata); + +#ifndef _sd_printf_ +# if __GNUC__ >= 4 +# define _sd_printf_(a,b) __attribute__((__format__(printf, a, b))) +# else +# define _sd_printf_(a,b) +# endif +#endif + +#ifndef _sd_sentinel_ +# define _sd_sentinel_ __attribute__((__sentinel__)) +#endif + +#ifndef _sd_packed_ +# define _sd_packed_ __attribute__((__packed__)) +#endif + +#ifndef _sd_pure_ +# define _sd_pure_ __attribute__((__pure__)) +#endif + +/* Note that strictly speaking __deprecated__ has been available before GCC 6. However, starting with GCC 6 + * it also works on enum values, which we are interested in. Since this is a developer-facing feature anyway + * (as opposed to build engineer-facing), let's hence conditionalize this to gcc 6, given that the developers + * are probably going to use something newer anyway. */ +#ifndef _sd_deprecated_ +# if __GNUC__ >= 6 +# define _sd_deprecated_ __attribute__((__deprecated__)) +# else +# define _sd_deprecated_ +# endif +#endif + +#ifndef _SD_STRINGIFY +# define _SD_XSTRINGIFY(x) #x +# define _SD_STRINGIFY(x) _SD_XSTRINGIFY(x) +#endif + +#ifndef _SD_BEGIN_DECLARATIONS +# ifdef __cplusplus +# define _SD_BEGIN_DECLARATIONS \ + extern "C" { \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ +# else +# define _SD_BEGIN_DECLARATIONS \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ +# endif +#endif + +#ifndef _SD_END_DECLARATIONS +# ifdef __cplusplus +# define _SD_END_DECLARATIONS \ + } \ + struct _sd_useless_cpp_struct_to_allow_trailing_semicolon_ +# else +# define _SD_END_DECLARATIONS \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ +# endif +#endif + +#ifndef _SD_ARRAY_STATIC +# if __STDC_VERSION__ >= 199901L +# define _SD_ARRAY_STATIC static +# else +# define _SD_ARRAY_STATIC +# endif +#endif + +#define _SD_DEFINE_POINTER_CLEANUP_FUNC(type, func) \ + static __inline__ void func##p(type **p) { \ + if (*p) \ + func(*p); \ + } \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ + +/* The following macro should be used in all public enums, to force 64bit wideness on them, so that we can + * freely extend them later on, without breaking compatibility. */ +#define _SD_ENUM_FORCE_S64(id) \ + _SD_##id##_INT64_MIN = INT64_MIN, \ + _SD_##id##_INT64_MAX = INT64_MAX + +#endif diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp-client.h b/src/libnm-systemd-core/src/systemd/sd-dhcp-client.h new file mode 100644 index 00000000..53002340 --- /dev/null +++ b/src/libnm-systemd-core/src/systemd/sd-dhcp-client.h @@ -0,0 +1,236 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcpclienthfoo +#define foosddhcpclienthfoo + +/*** + Copyright © 2013 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <net/ethernet.h> +#include <netinet/in.h> +#include <sys/types.h> +#include <stdbool.h> + +#include "sd-dhcp-lease.h" +#include "sd-dhcp-option.h" +#include "sd-event.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_DHCP_CLIENT_EVENT_STOP = 0, + SD_DHCP_CLIENT_EVENT_IP_ACQUIRE = 1, + SD_DHCP_CLIENT_EVENT_IP_CHANGE = 2, + SD_DHCP_CLIENT_EVENT_EXPIRED = 3, + SD_DHCP_CLIENT_EVENT_RENEW = 4, + SD_DHCP_CLIENT_EVENT_SELECTING = 5, + SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE = 6, /* Sent when we have not received a reply after the first few attempts. + * The client may want to start acquiring link-local addresses. */ +}; + +enum { + SD_DHCP_OPTION_PAD = 0, + SD_DHCP_OPTION_SUBNET_MASK = 1, + SD_DHCP_OPTION_TIME_OFFSET = 2, + SD_DHCP_OPTION_ROUTER = 3, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER = 6, + SD_DHCP_OPTION_LPR_SERVER = 9, + SD_DHCP_OPTION_HOST_NAME = 12, + SD_DHCP_OPTION_BOOT_FILE_SIZE = 13, + SD_DHCP_OPTION_DOMAIN_NAME = 15, + SD_DHCP_OPTION_ROOT_PATH = 17, + SD_DHCP_OPTION_ENABLE_IP_FORWARDING = 19, + SD_DHCP_OPTION_ENABLE_IP_FORWARDING_NL = 20, + SD_DHCP_OPTION_POLICY_FILTER = 21, + SD_DHCP_OPTION_INTERFACE_MDR = 22, + SD_DHCP_OPTION_INTERFACE_TTL = 23, + SD_DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24, + SD_DHCP_OPTION_INTERFACE_MTU = 26, + SD_DHCP_OPTION_BROADCAST = 28, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_ROUTER_DISCOVER = 31, + SD_DHCP_OPTION_STATIC_ROUTE = 33, + SD_DHCP_OPTION_NTP_SERVER = 42, + SD_DHCP_OPTION_VENDOR_SPECIFIC = 43, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_NETBIOS_NAMESERVER = 44, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_NETBIOS_NODETYPE = 46, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_NETBIOS_SCOPE = 47, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, + SD_DHCP_OPTION_OVERLOAD = 52, + SD_DHCP_OPTION_MESSAGE_TYPE = 53, + SD_DHCP_OPTION_SERVER_IDENTIFIER = 54, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, + SD_DHCP_OPTION_ERROR_MESSAGE = 56, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, + SD_DHCP_OPTION_RENEWAL_T1_TIME = 58, + SD_DHCP_OPTION_REBINDING_T2_TIME = 59, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60, + SD_DHCP_OPTION_CLIENT_IDENTIFIER = 61, + SD_DHCP_OPTION_SMTP_SERVER = 69, + SD_DHCP_OPTION_POP3_SERVER = 70, + SD_DHCP_OPTION_USER_CLASS = 77, + SD_DHCP_OPTION_FQDN = 81, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION = 82, + SD_DHCP_OPTION_NEW_POSIX_TIMEZONE = 100, + SD_DHCP_OPTION_NEW_TZDB_TIMEZONE = 101, + SD_DHCP_OPTION_DOMAIN_SEARCH_LIST = 119, + SD_DHCP_OPTION_SIP_SERVER = 120, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, + SD_DHCP_OPTION_MUD_URL = 161, + SD_DHCP_OPTION_PRIVATE_BASE = 224, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE = 249, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY = 252, + SD_DHCP_OPTION_PRIVATE_LAST = 254, + SD_DHCP_OPTION_END = 255, +}; + +/* Suboptions for SD_DHCP_OPTION_RELAY_AGENT_INFORMATION option */ +enum { + SD_DHCP_RELAY_AGENT_CIRCUIT_ID = 1, + SD_DHCP_RELAY_AGENT_REMOTE_ID = 2, +}; + +typedef struct sd_dhcp_client sd_dhcp_client; + +typedef int (*sd_dhcp_client_callback_t)(sd_dhcp_client *client, int event, void *userdata); +int sd_dhcp_client_set_callback( + sd_dhcp_client *client, + sd_dhcp_client_callback_t cb, + void *userdata); + +int sd_dhcp_client_set_request_option( + sd_dhcp_client *client, + uint8_t option); +int sd_dhcp_client_set_request_address( + sd_dhcp_client *client, + const struct in_addr *last_address); +int sd_dhcp_client_set_request_broadcast( + sd_dhcp_client *client, + int broadcast); +int sd_dhcp_client_set_ifindex( + sd_dhcp_client *client, + int interface_index); +int sd_dhcp_client_set_ifname( + sd_dhcp_client *client, + const char *interface_name); +const char *sd_dhcp_client_get_ifname(sd_dhcp_client *client); +int sd_dhcp_client_set_mac( + sd_dhcp_client *client, + const uint8_t *addr, + const uint8_t *bcast_addr, + size_t addr_len, + uint16_t arp_type); +int sd_dhcp_client_set_client_id( + sd_dhcp_client *client, + uint8_t type, + const uint8_t *data, + size_t data_len); +int sd_dhcp_client_set_iaid_duid( + sd_dhcp_client *client, + bool iaid_set, + uint32_t iaid, + uint16_t duid_type, + const void *duid, + size_t duid_len); +int sd_dhcp_client_set_iaid_duid_llt( + sd_dhcp_client *client, + bool iaid_set, + uint32_t iaid, + uint64_t llt_time); +int sd_dhcp_client_set_duid( + sd_dhcp_client *client, + uint16_t duid_type, + const void *duid, + size_t duid_len); +int sd_dhcp_client_set_duid_llt( + sd_dhcp_client *client, + uint64_t llt_time); +int sd_dhcp_client_get_client_id( + sd_dhcp_client *client, + uint8_t *type, + const uint8_t **data, + size_t *data_len); +int sd_dhcp_client_set_mtu( + sd_dhcp_client *client, + uint32_t mtu); +int sd_dhcp_client_set_max_attempts( + sd_dhcp_client *client, + uint64_t attempt); +int sd_dhcp_client_set_client_port( + sd_dhcp_client *client, + uint16_t port); +int sd_dhcp_client_set_hostname( + sd_dhcp_client *client, + const char *hostname); +int sd_dhcp_client_set_vendor_class_identifier( + sd_dhcp_client *client, + const char *vci); +int sd_dhcp_client_set_mud_url( + sd_dhcp_client *client, + const char *mudurl); +int sd_dhcp_client_set_user_class( + sd_dhcp_client *client, + char * const *user_class); +int sd_dhcp_client_get_lease( + sd_dhcp_client *client, + sd_dhcp_lease **ret); +int sd_dhcp_client_set_service_type( + sd_dhcp_client *client, + int type); +int sd_dhcp_client_set_fallback_lease_lifetime( + sd_dhcp_client *client, + uint32_t fallback_lease_lifetime); + +int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v); +int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v); + +int sd_dhcp_client_is_running(sd_dhcp_client *client); +int sd_dhcp_client_stop(sd_dhcp_client *client); +int sd_dhcp_client_start(sd_dhcp_client *client); +int sd_dhcp_client_send_release(sd_dhcp_client *client); +int sd_dhcp_client_send_decline(sd_dhcp_client *client); +int sd_dhcp_client_send_renew(sd_dhcp_client *client); + +sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client); +sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client); + +/* NOTE: anonymize parameter is used to initialize PRL memory with different + * options when using RFC7844 Anonymity Profiles */ +int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize); + +int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret); + +int sd_dhcp_client_attach_event( + sd_dhcp_client *client, + sd_event *event, + int64_t priority); +int sd_dhcp_client_detach_event(sd_dhcp_client *client); +sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_client, sd_dhcp_client_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp-lease.h b/src/libnm-systemd-core/src/systemd/sd-dhcp-lease.h new file mode 100644 index 00000000..5abf9a40 --- /dev/null +++ b/src/libnm-systemd-core/src/systemd/sd-dhcp-lease.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcpleasehfoo +#define foosddhcpleasehfoo + +/*** + Copyright © 2013 Intel Corporation. All rights reserved. + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <inttypes.h> +#include <net/ethernet.h> +#include <netinet/in.h> +#include <sys/types.h> + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp_lease sd_dhcp_lease; +typedef struct sd_dhcp_route sd_dhcp_route; + +sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease); +sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease); + +typedef enum sd_dhcp_lease_server_type_t { + SD_DHCP_LEASE_DNS, + SD_DHCP_LEASE_NTP, + SD_DHCP_LEASE_SIP, + SD_DHCP_LEASE_POP3, + SD_DHCP_LEASE_SMTP, + SD_DHCP_LEASE_LPR, + _SD_DHCP_LEASE_SERVER_TYPE_MAX, + _SD_DHCP_LEASE_SERVER_TYPE_INVALID = -EINVAL, + _SD_ENUM_FORCE_S64(DHCP_LEASE_SERVER_TYPE), +} sd_dhcp_lease_server_type_t; + +int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime); +int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1); +int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2); +int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_servers(sd_dhcp_lease *lease, sd_dhcp_lease_server_type_t what, const struct in_addr **addr); +int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_pop3(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_smtp(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_lpr(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu); +int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname); +int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains); +int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname); +int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path); +int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes); +int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len); +int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len); +int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **timezone); + +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination); +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length); +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway); +int sd_dhcp_route_get_option(sd_dhcp_route *route); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp-option.h b/src/libnm-systemd-core/src/systemd/sd-dhcp-option.h new file mode 100644 index 00000000..71aa479b --- /dev/null +++ b/src/libnm-systemd-core/src/systemd/sd-dhcp-option.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcpoptionhfoo +#define foosddhcpoptionhfoo + +/*** + Copyright © 2013 Intel Corporation. All rights reserved. + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <sys/types.h> + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp_option sd_dhcp_option; + +int sd_dhcp_option_new(uint8_t option, const void *data, size_t length, sd_dhcp_option **ret); +sd_dhcp_option *sd_dhcp_option_ref(sd_dhcp_option *ra); +sd_dhcp_option *sd_dhcp_option_unref(sd_dhcp_option *ra); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_option, sd_dhcp_option_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h b/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h new file mode 100644 index 00000000..e02d6763 --- /dev/null +++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcp6clienthfoo +#define foosddhcp6clienthfoo + +/*** + Copyright © 2014 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <net/ethernet.h> +#include <sys/types.h> + +#include "sd-dhcp6-lease.h" +#include "sd-dhcp6-option.h" +#include "sd-event.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_DHCP6_CLIENT_EVENT_STOP = 0, + SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE = 10, + SD_DHCP6_CLIENT_EVENT_RETRANS_MAX = 11, + SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE = 12, + SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST = 13, +}; + +enum { + SD_DHCP6_OPTION_CLIENTID = 1, + SD_DHCP6_OPTION_SERVERID = 2, + SD_DHCP6_OPTION_IA_NA = 3, + SD_DHCP6_OPTION_IA_TA = 4, + SD_DHCP6_OPTION_IAADDR = 5, + SD_DHCP6_OPTION_ORO = 6, + SD_DHCP6_OPTION_PREFERENCE = 7, + SD_DHCP6_OPTION_ELAPSED_TIME = 8, + SD_DHCP6_OPTION_RELAY_MSG = 9, + /* option code 10 is unassigned */ + SD_DHCP6_OPTION_AUTH = 11, + SD_DHCP6_OPTION_UNICAST = 12, + SD_DHCP6_OPTION_STATUS_CODE = 13, + SD_DHCP6_OPTION_RAPID_COMMIT = 14, + SD_DHCP6_OPTION_USER_CLASS = 15, + SD_DHCP6_OPTION_VENDOR_CLASS = 16, + SD_DHCP6_OPTION_VENDOR_OPTS = 17, + SD_DHCP6_OPTION_INTERFACE_ID = 18, + SD_DHCP6_OPTION_RECONF_MSG = 19, + SD_DHCP6_OPTION_RECONF_ACCEPT = 20, + + SD_DHCP6_OPTION_DNS_SERVERS = 23, /* RFC 3646 */ + SD_DHCP6_OPTION_DOMAIN_LIST = 24, /* RFC 3646 */ + SD_DHCP6_OPTION_IA_PD = 25, /* RFC 3633, prefix delegation */ + SD_DHCP6_OPTION_IA_PD_PREFIX = 26, /* RFC 3633, prefix delegation */ + + SD_DHCP6_OPTION_SNTP_SERVERS = 31, /* RFC 4075, deprecated */ + SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME = 32, /* RFC 8415, sec. 21.23 */ + + /* option code 35 is unassigned */ + + SD_DHCP6_OPTION_FQDN = 39, /* RFC 4704 */ + + SD_DHCP6_OPTION_NTP_SERVER = 56, /* RFC 5908 */ + SD_DHCP6_OPTION_MUD_URL = 112, /* RFC 8250 */ + + /* option codes 89-142 are unassigned */ + /* option codes 144-65535 are unassigned */ +}; + +typedef struct sd_dhcp6_client sd_dhcp6_client; + +typedef void (*sd_dhcp6_client_callback_t)(sd_dhcp6_client *client, int event, void *userdata); +int sd_dhcp6_client_set_callback( + sd_dhcp6_client *client, + sd_dhcp6_client_callback_t cb, + void *userdata); + +int sd_dhcp6_client_set_ifindex( + sd_dhcp6_client *client, + int interface_index); +int sd_dhcp6_client_set_ifname( + sd_dhcp6_client *client, + const char *interface_name); +const char * sd_dhcp6_client_get_ifname(sd_dhcp6_client *client); +int sd_dhcp6_client_set_local_address( + sd_dhcp6_client *client, + const struct in6_addr *local_address); +int sd_dhcp6_client_set_mac( + sd_dhcp6_client *client, + const uint8_t *addr, + size_t addr_len, + uint16_t arp_type); +int sd_dhcp6_client_set_duid( + sd_dhcp6_client *client, + uint16_t duid_type, + const void *duid, + size_t duid_len); +int sd_dhcp6_client_set_duid_llt( + sd_dhcp6_client *client, + uint64_t llt_time); +int sd_dhcp6_client_set_iaid( + sd_dhcp6_client *client, + uint32_t iaid); +int sd_dhcp6_client_get_iaid( + sd_dhcp6_client *client, + uint32_t *iaid); +int sd_dhcp6_client_duid_as_string( + sd_dhcp6_client *client, + char **duid); +int sd_dhcp6_client_set_fqdn( + sd_dhcp6_client *client, + const char *fqdn); +int sd_dhcp6_client_set_information_request( + sd_dhcp6_client *client, + int enabled); +int sd_dhcp6_client_get_information_request( + sd_dhcp6_client *client, + int *enabled); +int sd_dhcp6_client_set_request_option( + sd_dhcp6_client *client, + uint16_t option); +int sd_dhcp6_client_set_request_mud_url( + sd_dhcp6_client *client, + const char *mudurl); +int sd_dhcp6_client_set_request_user_class( + sd_dhcp6_client *client, + char * const *user_class); +int sd_dhcp6_client_set_request_vendor_class( + sd_dhcp6_client *client, + char * const *vendor_class); +int sd_dhcp6_client_set_prefix_delegation_hint( + sd_dhcp6_client *client, + uint8_t prefixlen, + const struct in6_addr *pd_address); +int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, + int *delegation); +int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, + int delegation); +int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client, + int *request); +int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, + int request); +int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, + uint32_t transaction_id); +int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, + sd_dhcp6_option *v); + +int sd_dhcp6_client_get_lease( + sd_dhcp6_client *client, + sd_dhcp6_lease **ret); + +int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v); + +int sd_dhcp6_client_stop(sd_dhcp6_client *client); +int sd_dhcp6_client_start(sd_dhcp6_client *client); +int sd_dhcp6_client_is_running(sd_dhcp6_client *client); +int sd_dhcp6_client_attach_event( + sd_dhcp6_client *client, + sd_event *event, + int64_t priority); +int sd_dhcp6_client_detach_event(sd_dhcp6_client *client); +sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client); +sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client); +sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client); +int sd_dhcp6_client_new(sd_dhcp6_client **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_client, sd_dhcp6_client_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h b/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h new file mode 100644 index 00000000..f77b31ac --- /dev/null +++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcp6leasehfoo +#define foosddhcp6leasehfoo + +/*** + Copyright © 2014-2015 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <netinet/in.h> + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp6_lease sd_dhcp6_lease; + +void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease); +int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, + struct in6_addr *addr, + uint32_t *lifetime_preferred, + uint32_t *lifetime_valid); +void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease); +int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix, + uint8_t *prefix_len, + uint32_t *lifetime_preferred, + uint32_t *lifetime_valid); + +int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **addrs); +int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains); +int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **addrs); +int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn); +int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **fqdn); + +sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease); +sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_lease, sd_dhcp6_lease_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp6-option.h b/src/libnm-systemd-core/src/systemd/sd-dhcp6-option.h new file mode 100644 index 00000000..ddb2c7ce --- /dev/null +++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-option.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcp6optionhfoo +#define foosddhcp6optionhfoo + +/*** + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <sys/types.h> + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp6_option sd_dhcp6_option; + +int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret); +sd_dhcp6_option *sd_dhcp6_option_ref(sd_dhcp6_option *ra); +sd_dhcp6_option *sd_dhcp6_option_unref(sd_dhcp6_option *ra); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_option, sd_dhcp6_option_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libnm-systemd-core/src/systemd/sd-event.h b/src/libnm-systemd-core/src/systemd/sd-event.h new file mode 100644 index 00000000..2ae2a0da --- /dev/null +++ b/src/libnm-systemd-core/src/systemd/sd-event.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdeventhfoo +#define foosdeventhfoo + +/*** + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <signal.h> +#include <sys/epoll.h> +#include <sys/inotify.h> +#include <sys/signalfd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> + +#include "_sd-common.h" + +/* + Why is this better than pure epoll? + + - Supports event source prioritization + - Scales better with a large number of time events because it does not require one timerfd each + - Automatically tries to coalesce timer events system-wide + - Handles signals, child PIDs, inotify events + - Supports systemd-style automatic watchdog event generation +*/ + +_SD_BEGIN_DECLARATIONS; + +#define SD_EVENT_DEFAULT ((sd_event *) 1) + +typedef struct sd_event sd_event; +typedef struct sd_event_source sd_event_source; + +enum { + SD_EVENT_OFF = 0, + SD_EVENT_ON = 1, + SD_EVENT_ONESHOT = -1 +}; + +enum { + SD_EVENT_INITIAL, + SD_EVENT_ARMED, + SD_EVENT_PENDING, + SD_EVENT_RUNNING, + SD_EVENT_EXITING, + SD_EVENT_FINISHED, + SD_EVENT_PREPARING +}; + +enum { + /* And everything in-between and outside is good too */ + SD_EVENT_PRIORITY_IMPORTANT = -100, + SD_EVENT_PRIORITY_NORMAL = 0, + SD_EVENT_PRIORITY_IDLE = 100 +}; + +typedef int (*sd_event_handler_t)(sd_event_source *s, void *userdata); +typedef int (*sd_event_io_handler_t)(sd_event_source *s, int fd, uint32_t revents, void *userdata); +typedef int (*sd_event_time_handler_t)(sd_event_source *s, uint64_t usec, void *userdata); +typedef int (*sd_event_signal_handler_t)(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata); +#if defined _GNU_SOURCE || (defined _POSIX_C_SOURCE && _POSIX_C_SOURCE >= 199309L) +typedef int (*sd_event_child_handler_t)(sd_event_source *s, const siginfo_t *si, void *userdata); +#else +typedef void* sd_event_child_handler_t; +#endif +typedef int (*sd_event_inotify_handler_t)(sd_event_source *s, const struct inotify_event *event, void *userdata); +typedef _sd_destroy_t sd_event_destroy_t; + +int sd_event_default(sd_event **e); + +int sd_event_new(sd_event **e); +sd_event* sd_event_ref(sd_event *e); +sd_event* sd_event_unref(sd_event *e); + +int sd_event_add_io(sd_event *e, sd_event_source **s, int fd, uint32_t events, sd_event_io_handler_t callback, void *userdata); +int sd_event_add_time(sd_event *e, sd_event_source **s, clockid_t clock, uint64_t usec, uint64_t accuracy, sd_event_time_handler_t callback, void *userdata); +int sd_event_add_time_relative(sd_event *e, sd_event_source **s, clockid_t clock, uint64_t usec, uint64_t accuracy, sd_event_time_handler_t callback, void *userdata); +int sd_event_add_signal(sd_event *e, sd_event_source **s, int sig, sd_event_signal_handler_t callback, void *userdata); +int sd_event_add_child(sd_event *e, sd_event_source **s, pid_t pid, int options, sd_event_child_handler_t callback, void *userdata); +int sd_event_add_child_pidfd(sd_event *e, sd_event_source **s, int pidfd, int options, sd_event_child_handler_t callback, void *userdata); +int sd_event_add_inotify(sd_event *e, sd_event_source **s, const char *path, uint32_t mask, sd_event_inotify_handler_t callback, void *userdata); +int sd_event_add_defer(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); +int sd_event_add_post(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); +int sd_event_add_exit(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); + +int sd_event_prepare(sd_event *e); +int sd_event_wait(sd_event *e, uint64_t usec); +int sd_event_dispatch(sd_event *e); +int sd_event_run(sd_event *e, uint64_t usec); +int sd_event_loop(sd_event *e); +int sd_event_exit(sd_event *e, int code); + +int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec); + +int sd_event_get_fd(sd_event *e); +int sd_event_get_state(sd_event *e); +int sd_event_get_tid(sd_event *e, pid_t *tid); +int sd_event_get_exit_code(sd_event *e, int *code); +int sd_event_set_watchdog(sd_event *e, int b); +int sd_event_get_watchdog(sd_event *e); +int sd_event_get_iteration(sd_event *e, uint64_t *ret); + +sd_event_source* sd_event_source_ref(sd_event_source *s); +sd_event_source* sd_event_source_unref(sd_event_source *s); +sd_event_source* sd_event_source_disable_unref(sd_event_source *s); + +sd_event *sd_event_source_get_event(sd_event_source *s); +void* sd_event_source_get_userdata(sd_event_source *s); +void* sd_event_source_set_userdata(sd_event_source *s, void *userdata); + +int sd_event_source_set_description(sd_event_source *s, const char *description); +int sd_event_source_get_description(sd_event_source *s, const char **description); +int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback); +int sd_event_source_get_pending(sd_event_source *s); +int sd_event_source_get_priority(sd_event_source *s, int64_t *priority); +int sd_event_source_set_priority(sd_event_source *s, int64_t priority); +int sd_event_source_get_enabled(sd_event_source *s, int *enabled); +int sd_event_source_set_enabled(sd_event_source *s, int enabled); +int sd_event_source_get_io_fd(sd_event_source *s); +int sd_event_source_set_io_fd(sd_event_source *s, int fd); +int sd_event_source_get_io_fd_own(sd_event_source *s); +int sd_event_source_set_io_fd_own(sd_event_source *s, int own); +int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events); +int sd_event_source_set_io_events(sd_event_source *s, uint32_t events); +int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents); +int sd_event_source_get_time(sd_event_source *s, uint64_t *usec); +int sd_event_source_set_time(sd_event_source *s, uint64_t usec); +int sd_event_source_set_time_relative(sd_event_source *s, uint64_t usec); +int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec); +int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec); +int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock); +int sd_event_source_get_signal(sd_event_source *s); +int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid); +int sd_event_source_get_child_pidfd(sd_event_source *s); +int sd_event_source_get_child_pidfd_own(sd_event_source *s); +int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own); +int sd_event_source_get_child_process_own(sd_event_source *s); +int sd_event_source_set_child_process_own(sd_event_source *s, int own); +#if defined _GNU_SOURCE || (defined _POSIX_C_SOURCE && _POSIX_C_SOURCE >= 199309L) +int sd_event_source_send_child_signal(sd_event_source *s, int sig, const siginfo_t *si, unsigned flags); +#else +int sd_event_source_send_child_signal(sd_event_source *s, int sig, const void *si, unsigned flags); +#endif +int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret); +int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback); +int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret); +int sd_event_source_get_floating(sd_event_source *s); +int sd_event_source_set_floating(sd_event_source *s, int b); +int sd_event_source_get_exit_on_failure(sd_event_source *s); +int sd_event_source_set_exit_on_failure(sd_event_source *s, int b); +int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval_usec, unsigned burst); +int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval_usec, unsigned *ret_burst); +int sd_event_source_is_ratelimited(sd_event_source *s); + +/* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */ +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event_source, sd_event_source_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event_source, sd_event_source_disable_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libnm-systemd-core/src/systemd/sd-id128.h b/src/libnm-systemd-core/src/systemd/sd-id128.h new file mode 100644 index 00000000..ab209c8c --- /dev/null +++ b/src/libnm-systemd-core/src/systemd/sd-id128.h @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdid128hfoo +#define foosdid128hfoo + +/*** + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <stdarg.h> +#include <string.h> + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* 128-bit ID APIs. See sd-id128(3) for more information. */ + +typedef union sd_id128 sd_id128_t; + +union sd_id128 { + uint8_t bytes[16]; + uint64_t qwords[2]; +}; + +#define SD_ID128_STRING_MAX 33 + +char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]); +int sd_id128_from_string(const char *s, sd_id128_t *ret); + +int sd_id128_randomize(sd_id128_t *ret); + +int sd_id128_get_machine(sd_id128_t *ret); +int sd_id128_get_boot(sd_id128_t *ret); +int sd_id128_get_invocation(sd_id128_t *ret); + +int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret); +int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret); + +#define SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ + { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \ + 0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }} + +#define SD_ID128_MAKE(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ + ((const sd_id128_t) SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15)) + +/* Note that SD_ID128_FORMAT_VAL will evaluate the passed argument 16 + * times. It is hence not a good idea to call this macro with an + * expensive function as parameter or an expression with side + * effects */ + +#define SD_ID128_FORMAT_STR "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" +#define SD_ID128_FORMAT_VAL(x) (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15] + +/* Like SD_ID128_FORMAT_STR, but formats as UUID, not in plain format */ +#define SD_ID128_UUID_FORMAT_STR "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" + +#define SD_ID128_CONST_STR(x) \ + ((const char[SD_ID128_STRING_MAX]) { \ + ((x).bytes[0] >> 4) >= 10 ? 'a' + ((x).bytes[0] >> 4) - 10 : '0' + ((x).bytes[0] >> 4), \ + ((x).bytes[0] & 15) >= 10 ? 'a' + ((x).bytes[0] & 15) - 10 : '0' + ((x).bytes[0] & 15), \ + ((x).bytes[1] >> 4) >= 10 ? 'a' + ((x).bytes[1] >> 4) - 10 : '0' + ((x).bytes[1] >> 4), \ + ((x).bytes[1] & 15) >= 10 ? 'a' + ((x).bytes[1] & 15) - 10 : '0' + ((x).bytes[1] & 15), \ + ((x).bytes[2] >> 4) >= 10 ? 'a' + ((x).bytes[2] >> 4) - 10 : '0' + ((x).bytes[2] >> 4), \ + ((x).bytes[2] & 15) >= 10 ? 'a' + ((x).bytes[2] & 15) - 10 : '0' + ((x).bytes[2] & 15), \ + ((x).bytes[3] >> 4) >= 10 ? 'a' + ((x).bytes[3] >> 4) - 10 : '0' + ((x).bytes[3] >> 4), \ + ((x).bytes[3] & 15) >= 10 ? 'a' + ((x).bytes[3] & 15) - 10 : '0' + ((x).bytes[3] & 15), \ + ((x).bytes[4] >> 4) >= 10 ? 'a' + ((x).bytes[4] >> 4) - 10 : '0' + ((x).bytes[4] >> 4), \ + ((x).bytes[4] & 15) >= 10 ? 'a' + ((x).bytes[4] & 15) - 10 : '0' + ((x).bytes[4] & 15), \ + ((x).bytes[5] >> 4) >= 10 ? 'a' + ((x).bytes[5] >> 4) - 10 : '0' + ((x).bytes[5] >> 4), \ + ((x).bytes[5] & 15) >= 10 ? 'a' + ((x).bytes[5] & 15) - 10 : '0' + ((x).bytes[5] & 15), \ + ((x).bytes[6] >> 4) >= 10 ? 'a' + ((x).bytes[6] >> 4) - 10 : '0' + ((x).bytes[6] >> 4), \ + ((x).bytes[6] & 15) >= 10 ? 'a' + ((x).bytes[6] & 15) - 10 : '0' + ((x).bytes[6] & 15), \ + ((x).bytes[7] >> 4) >= 10 ? 'a' + ((x).bytes[7] >> 4) - 10 : '0' + ((x).bytes[7] >> 4), \ + ((x).bytes[7] & 15) >= 10 ? 'a' + ((x).bytes[7] & 15) - 10 : '0' + ((x).bytes[7] & 15), \ + ((x).bytes[8] >> 4) >= 10 ? 'a' + ((x).bytes[8] >> 4) - 10 : '0' + ((x).bytes[8] >> 4), \ + ((x).bytes[8] & 15) >= 10 ? 'a' + ((x).bytes[8] & 15) - 10 : '0' + ((x).bytes[8] & 15), \ + ((x).bytes[9] >> 4) >= 10 ? 'a' + ((x).bytes[9] >> 4) - 10 : '0' + ((x).bytes[9] >> 4), \ + ((x).bytes[9] & 15) >= 10 ? 'a' + ((x).bytes[9] & 15) - 10 : '0' + ((x).bytes[9] & 15), \ + ((x).bytes[10] >> 4) >= 10 ? 'a' + ((x).bytes[10] >> 4) - 10 : '0' + ((x).bytes[10] >> 4), \ + ((x).bytes[10] & 15) >= 10 ? 'a' + ((x).bytes[10] & 15) - 10 : '0' + ((x).bytes[10] & 15), \ + ((x).bytes[11] >> 4) >= 10 ? 'a' + ((x).bytes[11] >> 4) - 10 : '0' + ((x).bytes[11] >> 4), \ + ((x).bytes[11] & 15) >= 10 ? 'a' + ((x).bytes[11] & 15) - 10 : '0' + ((x).bytes[11] & 15), \ + ((x).bytes[12] >> 4) >= 10 ? 'a' + ((x).bytes[12] >> 4) - 10 : '0' + ((x).bytes[12] >> 4), \ + ((x).bytes[12] & 15) >= 10 ? 'a' + ((x).bytes[12] & 15) - 10 : '0' + ((x).bytes[12] & 15), \ + ((x).bytes[13] >> 4) >= 10 ? 'a' + ((x).bytes[13] >> 4) - 10 : '0' + ((x).bytes[13] >> 4), \ + ((x).bytes[13] & 15) >= 10 ? 'a' + ((x).bytes[13] & 15) - 10 : '0' + ((x).bytes[13] & 15), \ + ((x).bytes[14] >> 4) >= 10 ? 'a' + ((x).bytes[14] >> 4) - 10 : '0' + ((x).bytes[14] >> 4), \ + ((x).bytes[14] & 15) >= 10 ? 'a' + ((x).bytes[14] & 15) - 10 : '0' + ((x).bytes[14] & 15), \ + ((x).bytes[15] >> 4) >= 10 ? 'a' + ((x).bytes[15] >> 4) - 10 : '0' + ((x).bytes[15] >> 4), \ + ((x).bytes[15] & 15) >= 10 ? 'a' + ((x).bytes[15] & 15) - 10 : '0' + ((x).bytes[15] & 15), \ + 0 }) + +#define SD_ID128_MAKE_STR(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \ + #a #b #c #d #e #f #g #h #i #j #k #l #m #n #o #p + +_sd_pure_ static __inline__ int sd_id128_equal(sd_id128_t a, sd_id128_t b) { + return memcmp(&a, &b, 16) == 0; +} + +_sd_pure_ static __inline__ int sd_id128_is_null(sd_id128_t a) { + return a.qwords[0] == 0 && a.qwords[1] == 0; +} + +_sd_pure_ static __inline__ int sd_id128_is_allf(sd_id128_t a) { + return a.qwords[0] == UINT64_C(0xFFFFFFFFFFFFFFFF) && a.qwords[1] == UINT64_C(0xFFFFFFFFFFFFFFFF); +} + +#define SD_ID128_NULL ((const sd_id128_t) { .qwords = { 0, 0 }}) +#define SD_ID128_ALLF ((const sd_id128_t) { .qwords = { UINT64_C(0xFFFFFFFFFFFFFFFF), UINT64_C(0xFFFFFFFFFFFFFFFF) }}) + +_sd_pure_ static __inline__ int sd_id128_in_setv(sd_id128_t a, va_list ap) { + for (;;) { + sd_id128_t b = va_arg(ap, sd_id128_t); + + if (sd_id128_is_null(b)) + return 0; + + if (sd_id128_equal(a, b)) + return 1; + } +} + +_sd_pure_ static __inline__ int sd_id128_in_set_sentinel(sd_id128_t a, ...) { + va_list ap; + int r; + + va_start(ap, a); + r = sd_id128_in_setv(a, ap); + va_end(ap); + + return r; +} + +#define sd_id128_in_set(a, ...) \ + sd_id128_in_set_sentinel(a, ##__VA_ARGS__, SD_ID128_NULL) + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libnm-systemd-core/src/systemd/sd-ipv4acd.h b/src/libnm-systemd-core/src/systemd/sd-ipv4acd.h new file mode 100644 index 00000000..1e89a81b --- /dev/null +++ b/src/libnm-systemd-core/src/systemd/sd-ipv4acd.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdipv4acdfoo +#define foosdipv4acdfoo + +/*** + Copyright © 2014 Axis Communications AB. All rights reserved. + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <net/ethernet.h> +#include <netinet/in.h> +#include <stdbool.h> + +#include "sd-event.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_IPV4ACD_EVENT_STOP = 0, + SD_IPV4ACD_EVENT_BIND = 1, + SD_IPV4ACD_EVENT_CONFLICT = 2, +}; + +typedef struct sd_ipv4acd sd_ipv4acd; +typedef void (*sd_ipv4acd_callback_t)(sd_ipv4acd *acd, int event, void *userdata); + +int sd_ipv4acd_detach_event(sd_ipv4acd *acd); +int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority); +int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address); +int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata); +int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr); +int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int interface_index); +int sd_ipv4acd_get_ifindex(sd_ipv4acd *acd); +int sd_ipv4acd_set_ifname(sd_ipv4acd *acd, const char *interface_name); +const char *sd_ipv4acd_get_ifname(sd_ipv4acd *acd); +int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address); +int sd_ipv4acd_is_running(sd_ipv4acd *acd); +int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts); +int sd_ipv4acd_stop(sd_ipv4acd *acd); +sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *acd); +sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *acd); +int sd_ipv4acd_new(sd_ipv4acd **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4acd, sd_ipv4acd_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libnm-systemd-core/src/systemd/sd-ipv4ll.h b/src/libnm-systemd-core/src/systemd/sd-ipv4ll.h new file mode 100644 index 00000000..bf5596ab --- /dev/null +++ b/src/libnm-systemd-core/src/systemd/sd-ipv4ll.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdipv4llfoo +#define foosdipv4llfoo + +/*** + Copyright © 2014 Axis Communications AB. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <net/ethernet.h> +#include <netinet/in.h> + +#include "sd-event.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_IPV4LL_EVENT_STOP = 0, + SD_IPV4LL_EVENT_BIND = 1, + SD_IPV4LL_EVENT_CONFLICT = 2, +}; + +typedef struct sd_ipv4ll sd_ipv4ll; +typedef void (*sd_ipv4ll_callback_t)(sd_ipv4ll *ll, int event, void *userdata); + +int sd_ipv4ll_detach_event(sd_ipv4ll *ll); +int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority); +int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address); +int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata); +int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr); +int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int interface_index); +int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll); +int sd_ipv4ll_set_ifname(sd_ipv4ll *ll, const char *interface_name); +const char *sd_ipv4ll_get_ifname(sd_ipv4ll *ll); +int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address); +int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed); +int sd_ipv4ll_is_running(sd_ipv4ll *ll); +int sd_ipv4ll_restart(sd_ipv4ll *ll); +int sd_ipv4ll_start(sd_ipv4ll *ll); +int sd_ipv4ll_stop(sd_ipv4ll *ll); +sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll); +sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll); +int sd_ipv4ll_new(sd_ipv4ll **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4ll, sd_ipv4ll_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libnm-systemd-core/src/systemd/sd-lldp.h b/src/libnm-systemd-core/src/systemd/sd-lldp.h new file mode 100644 index 00000000..64047ee8 --- /dev/null +++ b/src/libnm-systemd-core/src/systemd/sd-lldp.h @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdlldphfoo +#define foosdlldphfoo + +/*** + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <inttypes.h> +#include <net/ethernet.h> +#include <sys/types.h> + +#include "sd-event.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* IEEE 802.1AB-2009 Clause 8: TLV Types */ +enum { + SD_LLDP_TYPE_END = 0, + SD_LLDP_TYPE_CHASSIS_ID = 1, + SD_LLDP_TYPE_PORT_ID = 2, + SD_LLDP_TYPE_TTL = 3, + SD_LLDP_TYPE_PORT_DESCRIPTION = 4, + SD_LLDP_TYPE_SYSTEM_NAME = 5, + SD_LLDP_TYPE_SYSTEM_DESCRIPTION = 6, + SD_LLDP_TYPE_SYSTEM_CAPABILITIES = 7, + SD_LLDP_TYPE_MGMT_ADDRESS = 8, + SD_LLDP_TYPE_PRIVATE = 127, +}; + +/* IEEE 802.1AB-2009 Clause 8.5.2: Chassis subtypes */ +enum { + SD_LLDP_CHASSIS_SUBTYPE_RESERVED = 0, + SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT = 1, + SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS = 2, + SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT = 3, + SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS = 4, + SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS = 5, + SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME = 6, + SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED = 7, +}; + +/* IEEE 802.1AB-2009 Clause 8.5.3: Port subtype */ +enum { + SD_LLDP_PORT_SUBTYPE_RESERVED = 0, + SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS = 1, + SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT = 2, + SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS = 3, + SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS = 4, + SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME = 5, + SD_LLDP_PORT_SUBTYPE_AGENT_CIRCUIT_ID = 6, + SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED = 7, +}; + +/* IEEE 802.1AB-2009 Clause 8.5.8: System capabilities */ +enum { + SD_LLDP_SYSTEM_CAPABILITIES_OTHER = 1 << 0, + SD_LLDP_SYSTEM_CAPABILITIES_REPEATER = 1 << 1, + SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE = 1 << 2, + SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP = 1 << 3, + SD_LLDP_SYSTEM_CAPABILITIES_ROUTER = 1 << 4, + SD_LLDP_SYSTEM_CAPABILITIES_PHONE = 1 << 5, + SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS = 1 << 6, + SD_LLDP_SYSTEM_CAPABILITIES_STATION = 1 << 7, + SD_LLDP_SYSTEM_CAPABILITIES_CVLAN = 1 << 8, + SD_LLDP_SYSTEM_CAPABILITIES_SVLAN = 1 << 9, + SD_LLDP_SYSTEM_CAPABILITIES_TPMR = 1 << 10, +}; + +#define SD_LLDP_SYSTEM_CAPABILITIES_ALL UINT16_MAX + +#define SD_LLDP_SYSTEM_CAPABILITIES_ALL_ROUTERS \ + ((uint16_t) \ + (SD_LLDP_SYSTEM_CAPABILITIES_REPEATER| \ + SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE| \ + SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP| \ + SD_LLDP_SYSTEM_CAPABILITIES_ROUTER| \ + SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS| \ + SD_LLDP_SYSTEM_CAPABILITIES_CVLAN| \ + SD_LLDP_SYSTEM_CAPABILITIES_SVLAN| \ + SD_LLDP_SYSTEM_CAPABILITIES_TPMR)) + +#define SD_LLDP_OUI_802_1 (uint8_t[]) { 0x00, 0x80, 0xc2 } +#define SD_LLDP_OUI_802_3 (uint8_t[]) { 0x00, 0x12, 0x0f } + +#define SD_LLDP_OUI_MUD (uint8_t[]) { 0x00, 0x00, 0x5E } +#define SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION 0x01 + +/* IEEE 802.1AB-2009 Annex E */ +enum { + SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID = 1, + SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID = 2, + SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME = 3, + SD_LLDP_OUI_802_1_SUBTYPE_PROTOCOL_IDENTITY = 4, + SD_LLDP_OUI_802_1_SUBTYPE_VID_USAGE_DIGEST = 5, + SD_LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID = 6, + SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION = 7, +}; + +/* IEEE 802.1AB-2009 Annex F */ +enum { + SD_LLDP_OUI_802_3_SUBTYPE_MAC_PHY_CONFIG_STATUS = 1, + SD_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI = 2, + SD_LLDP_OUI_802_3_SUBTYPE_LINK_AGGREGATION = 3, + SD_LLDP_OUI_802_3_SUBTYPE_MAXIMUM_FRAME_SIZE = 4, +}; + +typedef struct sd_lldp sd_lldp; +typedef struct sd_lldp_neighbor sd_lldp_neighbor; + +typedef enum sd_lldp_event_t { + SD_LLDP_EVENT_ADDED, + SD_LLDP_EVENT_REMOVED, + SD_LLDP_EVENT_UPDATED, + SD_LLDP_EVENT_REFRESHED, + _SD_LLDP_EVENT_MAX, + _SD_LLDP_EVENT_INVALID = -EINVAL, + _SD_ENUM_FORCE_S64(LLDP_EVENT), +} sd_lldp_event_t; + +typedef void (*sd_lldp_callback_t)(sd_lldp *lldp, sd_lldp_event_t event, sd_lldp_neighbor *n, void *userdata); + +int sd_lldp_new(sd_lldp **ret); +sd_lldp* sd_lldp_ref(sd_lldp *lldp); +sd_lldp* sd_lldp_unref(sd_lldp *lldp); + +int sd_lldp_start(sd_lldp *lldp); +int sd_lldp_stop(sd_lldp *lldp); + +int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority); +int sd_lldp_detach_event(sd_lldp *lldp); +sd_event *sd_lldp_get_event(sd_lldp *lldp); + +int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata); +int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex); +int sd_lldp_set_ifname(sd_lldp *lldp, const char *ifname); +const char *sd_lldp_get_ifname(sd_lldp *lldp); + +/* Controls how much and what to store in the neighbors database */ +int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t n); +int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask); +int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *address); + +int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***neighbors); + +int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size); +sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n); +sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n); + +/* Access to LLDP frame metadata */ +int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address); +int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address); +int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret); +int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size); + +/* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */ +int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size); +int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size); +int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec); +int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret); +int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret); + +/* Low-level, iterative TLV access. This is for everything else, it iteratively goes through all available TLVs + * (including the ones covered with the calls above), and allows multiple TLVs for the same fields. */ +int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n); +int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n); +int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type); +int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type); +int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype); +int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype); +int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp, sd_lldp_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_neighbor, sd_lldp_neighbor_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libnm-systemd-core/src/systemd/sd-ndisc.h b/src/libnm-systemd-core/src/systemd/sd-ndisc.h new file mode 100644 index 00000000..6088def1 --- /dev/null +++ b/src/libnm-systemd-core/src/systemd/sd-ndisc.h @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdndiscfoo +#define foosdndiscfoo + +/*** + Copyright © 2014 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <inttypes.h> +#include <net/ethernet.h> +#include <netinet/in.h> +#include <sys/types.h> + +#include "sd-event.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* Neighbor Discovery Options, RFC 4861, Section 4.6 and + * https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-5 */ +enum { + SD_NDISC_OPTION_SOURCE_LL_ADDRESS = 1, + SD_NDISC_OPTION_TARGET_LL_ADDRESS = 2, + SD_NDISC_OPTION_PREFIX_INFORMATION = 3, + SD_NDISC_OPTION_MTU = 5, + SD_NDISC_OPTION_ROUTE_INFORMATION = 24, + SD_NDISC_OPTION_RDNSS = 25, + SD_NDISC_OPTION_FLAGS_EXTENSION = 26, + SD_NDISC_OPTION_DNSSL = 31, + SD_NDISC_OPTION_CAPTIVE_PORTAL = 37, +}; + +/* Route preference, RFC 4191, Section 2.1 */ +enum { + SD_NDISC_PREFERENCE_LOW = 3U, + SD_NDISC_PREFERENCE_MEDIUM = 0U, + SD_NDISC_PREFERENCE_HIGH = 1U, +}; + +typedef struct sd_ndisc sd_ndisc; +typedef struct sd_ndisc_router sd_ndisc_router; + +typedef enum sd_ndisc_event_t { + SD_NDISC_EVENT_TIMEOUT, + SD_NDISC_EVENT_ROUTER, + _SD_NDISC_EVENT_MAX, + _SD_NDISC_EVENT_INVALID = -EINVAL, + _SD_ENUM_FORCE_S64(NDISC_EVENT), +} sd_ndisc_event_t; + +typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router *rt, void *userdata); + +int sd_ndisc_new(sd_ndisc **ret); +sd_ndisc *sd_ndisc_ref(sd_ndisc *nd); +sd_ndisc *sd_ndisc_unref(sd_ndisc *nd); + +int sd_ndisc_start(sd_ndisc *nd); +int sd_ndisc_stop(sd_ndisc *nd); + +int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority); +int sd_ndisc_detach_event(sd_ndisc *nd); +sd_event *sd_ndisc_get_event(sd_ndisc *nd); + +int sd_ndisc_set_callback(sd_ndisc *nd, sd_ndisc_callback_t cb, void *userdata); +int sd_ndisc_set_ifindex(sd_ndisc *nd, int interface_index); +int sd_ndisc_set_ifname(sd_ndisc *nd, const char *interface_name); +const char *sd_ndisc_get_ifname(sd_ndisc *nd); +int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr); + +int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *ret); +int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret); + +int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size); +sd_ndisc_router *sd_ndisc_router_ref(sd_ndisc_router *rt); +sd_ndisc_router *sd_ndisc_router_unref(sd_ndisc_router *rt); + +int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr); +int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); +int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size); + +int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags); +int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret); +int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime); +int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret); + +/* Generic option access */ +int sd_ndisc_router_option_rewind(sd_ndisc_router *rt); +int sd_ndisc_router_option_next(sd_ndisc_router *rt); +int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type); +int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size); + +/* Specific option access: SD_NDISC_OPTION_PREFIX_INFORMATION */ +int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_t *ret); +int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_t *ret); +int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr); +int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *prefixlen); + +/* Specific option access: SD_NDISC_OPTION_ROUTE_INFORMATION */ +int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_t *ret); +int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr); +int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *prefixlen); +int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret); + +/* Specific option access: SD_NDISC_OPTION_RDNSS */ +int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret); +int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_t *ret); + +/* Specific option access: SD_NDISC_OPTION_DNSSL */ +int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret); +int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_router, sd_ndisc_router_unref); + +_SD_END_DECLARATIONS; + +#endif |