diff --git a/man/networkctl.xml b/man/networkctl.xml index e85718269a..8370e72cf9 100644 --- a/man/networkctl.xml +++ b/man/networkctl.xml @@ -495,6 +495,21 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR) + + + + persistent-storage + BOOL + + + Notify systemd-networkd.service that the persistent storage for the + service is ready. This is called by + systemd-networkd-persistent-storage.service. Usually, this command should not + be called manually by users or administrators. + + + + diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 9be90e17c5..472add2f98 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -428,7 +428,13 @@ Defaults to no. Further settings for the DHCP server may be set in the [DHCPServer] section described below. - + Even if this is enabled, the DHCP server will not be started automatically. It will be + started after systemd-networkd-persistent-storage.service is started, which + calls networkctl persistent-storage yes. See + networkctl1 + for more details. + + diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 3601be25fb..822d660fda 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -83,7 +83,7 @@ triple_timestamp* triple_timestamp_now(triple_timestamp *ts) { return ts; } -static usec_t map_clock_usec_internal(usec_t from, usec_t from_base, usec_t to_base) { +usec_t map_clock_usec_raw(usec_t from, usec_t from_base, usec_t to_base) { /* Maps the time 'from' between two clocks, based on a common reference point where the first clock * is at 'from_base' and the second clock at 'to_base'. Basically calculates: @@ -121,7 +121,7 @@ usec_t map_clock_usec(usec_t from, clockid_t from_clock, clockid_t to_clock) { if (from == USEC_INFINITY) return from; - return map_clock_usec_internal(from, now(from_clock), now(to_clock)); + return map_clock_usec_raw(from, now(from_clock), now(to_clock)); } dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) { @@ -150,8 +150,8 @@ triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) nowr = now(CLOCK_REALTIME); ts->realtime = u; - ts->monotonic = map_clock_usec_internal(u, nowr, now(CLOCK_MONOTONIC)); - ts->boottime = map_clock_usec_internal(u, nowr, now(CLOCK_BOOTTIME)); + ts->monotonic = map_clock_usec_raw(u, nowr, now(CLOCK_MONOTONIC)); + ts->boottime = map_clock_usec_raw(u, nowr, now(CLOCK_BOOTTIME)); return ts; } @@ -169,8 +169,8 @@ triple_timestamp* triple_timestamp_from_boottime(triple_timestamp *ts, usec_t u) nowb = now(CLOCK_BOOTTIME); ts->boottime = u; - ts->monotonic = map_clock_usec_internal(u, nowb, now(CLOCK_MONOTONIC)); - ts->realtime = map_clock_usec_internal(u, nowb, now(CLOCK_REALTIME)); + ts->monotonic = map_clock_usec_raw(u, nowb, now(CLOCK_MONOTONIC)); + ts->realtime = map_clock_usec_raw(u, nowb, now(CLOCK_REALTIME)); return ts; } @@ -199,8 +199,8 @@ dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) { } nowm = now(CLOCK_BOOTTIME); - ts->monotonic = map_clock_usec_internal(u, nowm, now(CLOCK_MONOTONIC)); - ts->realtime = map_clock_usec_internal(u, nowm, now(CLOCK_REALTIME)); + ts->monotonic = map_clock_usec_raw(u, nowm, now(CLOCK_MONOTONIC)); + ts->realtime = map_clock_usec_raw(u, nowm, now(CLOCK_REALTIME)); return ts; } diff --git a/src/basic/time-util.h b/src/basic/time-util.h index 29373477f4..a3c219ecdd 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -77,6 +77,7 @@ typedef enum TimestampStyle { usec_t now(clockid_t clock); nsec_t now_nsec(clockid_t clock); +usec_t map_clock_usec_raw(usec_t from, usec_t from_base, usec_t to_base); usec_t map_clock_usec(usec_t from, clockid_t from_clock, clockid_t to_clock); dual_timestamp* dual_timestamp_now(dual_timestamp *ts); diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index 4025dd3632..e4ba77ae29 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -10,6 +10,8 @@ #include "alloc-util.h" #include "dhcp-option.h" #include "dhcp-server-internal.h" +#include "dns-domain.h" +#include "hostname-util.h" #include "memory-util.h" #include "ordered-set.h" #include "strv.h" @@ -396,27 +398,56 @@ int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t c } int dhcp_option_parse_string(const uint8_t *option, size_t len, char **ret) { + _cleanup_free_ char *string = NULL; int r; 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 - */ - r = make_cstring((const char *) option, len, MAKE_CSTRING_ALLOW_TRAILING_NUL, &string); - if (r < 0) - return r; - - free_and_replace(*ret, string); + if (len <= 0) { + *ret = NULL; + return 0; } + /* One trailing NUL byte is OK, we don't mind. See: + * https://github.com/systemd/systemd/issues/1337 */ + r = make_cstring((const char *) option, len, MAKE_CSTRING_ALLOW_TRAILING_NUL, &string); + if (r < 0) + return r; + + if (!string_is_safe(string) || !utf8_is_valid(string)) + return -EINVAL; + + *ret = TAKE_PTR(string); + return 0; +} + +int dhcp_option_parse_hostname(const uint8_t *option, size_t len, char **ret) { + _cleanup_free_ char *hostname = NULL; + int r; + + assert(option); + assert(ret); + + r = dhcp_option_parse_string(option, len, &hostname); + if (r < 0) + return r; + + if (!hostname) { + *ret = NULL; + return 0; + } + + if (!hostname_is_valid(hostname, 0)) + return -EINVAL; + + r = dns_name_is_valid(hostname); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + *ret = TAKE_PTR(hostname); return 0; } diff --git a/src/libsystemd-network/dhcp-option.h b/src/libsystemd-network/dhcp-option.h index 425f5b5016..aaa8f847b1 100644 --- a/src/libsystemd-network/dhcp-option.h +++ b/src/libsystemd-network/dhcp-option.h @@ -44,3 +44,4 @@ int dhcp_option_parse( char **ret_error_message); int dhcp_option_parse_string(const uint8_t *option, size_t len, char **ret); +int dhcp_option_parse_hostname(const uint8_t *option, size_t len, char **ret); diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index 6e4404fb73..8f5707c8e2 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -75,6 +75,8 @@ struct sd_dhcp_server { char *agent_circuit_id; char *agent_remote_id; + + char *lease_file; }; typedef struct DHCPRequest { diff --git a/src/libsystemd-network/dhcp-server-lease-internal.h b/src/libsystemd-network/dhcp-server-lease-internal.h index 49bc173307..518c151094 100644 --- a/src/libsystemd-network/dhcp-server-lease-internal.h +++ b/src/libsystemd-network/dhcp-server-lease-internal.h @@ -5,6 +5,7 @@ #include "dhcp-client-id-internal.h" #include "dhcp-server-internal.h" +#include "json.h" #include "time-util.h" typedef struct sd_dhcp_server_lease { @@ -31,3 +32,9 @@ int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *r int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server); sd_dhcp_server_lease* dhcp_server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req); + +int dhcp_server_bound_leases_append_json(sd_dhcp_server *server, JsonVariant **v); +int dhcp_server_static_leases_append_json(sd_dhcp_server *server, JsonVariant **v); + +int dhcp_server_save_leases(sd_dhcp_server *server); +int dhcp_server_load_leases(sd_dhcp_server *server); diff --git a/src/libsystemd-network/fuzz-dhcp-server.c b/src/libsystemd-network/fuzz-dhcp-server.c index 3e1efcf7fe..f30ea6353e 100644 --- a/src/libsystemd-network/fuzz-dhcp-server.c +++ b/src/libsystemd-network/fuzz-dhcp-server.c @@ -7,6 +7,9 @@ #include "sd-dhcp-server.c" #include "fuzz.h" +#include "path-util.h" +#include "rm-rf.h" +#include "tmpfile-util.h" /* stub out network so that the server doesn't send */ ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { @@ -31,7 +34,7 @@ static int add_lease(sd_dhcp_server *server, const struct in_addr *server_addres .n_ref = 1, .address = htobe32(UINT32_C(10) << 24 | i), .chaddr = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, - .expiration = UINT64_MAX, + .expiration = usec_add(now(CLOCK_BOOTTIME), USEC_PER_DAY), .gateway = server_address->s_addr, .hlen = ETH_ALEN, .htype = ARPHRD_ETHER, @@ -62,9 +65,11 @@ static int add_static_lease(sd_dhcp_server *server, uint8_t i) { } int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; struct in_addr address = { .s_addr = htobe32(UINT32_C(10) << 24 | UINT32_C(1))}; _cleanup_free_ uint8_t *duped = NULL; + _cleanup_free_ char *lease_file = NULL; if (size < sizeof(DHCPMessage)) return 0; @@ -73,8 +78,12 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { assert_se(duped = memdup(data, size)); + assert_se(mkdtemp_malloc(NULL, &tmpdir) >= 0); + assert_se(lease_file = path_join(tmpdir, "leases")); + assert_se(sd_dhcp_server_new(&server, 1) >= 0); assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0); + assert_se(sd_dhcp_server_set_lease_file(server, lease_file) >= 0); server->fd = open("/dev/null", O_RDWR|O_CLOEXEC|O_NOCTTY); assert_se(server->fd >= 0); assert_se(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0) >= 0); @@ -89,5 +98,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { (void) dhcp_server_handle_message(server, (DHCPMessage*) duped, size, NULL); + assert_se(dhcp_server_save_leases(server) >= 0); + server->bound_leases_by_address = hashmap_free(server->bound_leases_by_address); + server->bound_leases_by_client_id = hashmap_free(server->bound_leases_by_client_id); + assert_se(dhcp_server_load_leases(server) >= 0); + return 0; } diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 401e70823a..37f4b3b2c9 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -832,12 +832,16 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void break; - case SD_DHCP_OPTION_ROOT_PATH: - r = dhcp_option_parse_string(option, len, &lease->root_path); + case SD_DHCP_OPTION_ROOT_PATH: { + _cleanup_free_ char *p = NULL; + + r = dhcp_option_parse_string(option, len, &p); if (r < 0) log_debug_errno(r, "Failed to parse root path, ignoring: %m"); - break; + free_and_replace(lease->root_path, p); + break; + } case SD_DHCP_OPTION_RENEWAL_TIME: r = lease_parse_be32_seconds(option, len, /* max_as_infinity = */ true, &lease->t1); if (r < 0) diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c index db1d0464ae..f4c7e8d0a4 100644 --- a/src/libsystemd-network/sd-dhcp-server-lease.c +++ b/src/libsystemd-network/sd-dhcp-server-lease.c @@ -1,6 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "dhcp-server-lease-internal.h" +#include "fd-util.h" +#include "fs-util.h" +#include "mkdir.h" +#include "tmpfile-util.h" static sd_dhcp_server_lease* dhcp_server_lease_free(sd_dhcp_server_lease *lease) { if (!lease) @@ -204,3 +208,262 @@ int sd_dhcp_server_set_static_lease( TAKE_PTR(lease); return 0; } + +static int dhcp_server_lease_append_json(sd_dhcp_server_lease *lease, JsonVariant **ret) { + assert(lease); + assert(ret); + + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_BYTE_ARRAY("ClientId", lease->client_id.raw, lease->client_id.size), + JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("Address", &(struct in_addr) { .s_addr = lease->address }), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", lease->hostname))); +} + +int dhcp_server_bound_leases_append_json(sd_dhcp_server *server, JsonVariant **v) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + sd_dhcp_server_lease *lease; + usec_t now_b, now_r; + int r; + + assert(server); + assert(v); + + r = sd_event_now(server->event, CLOCK_BOOTTIME, &now_b); + if (r < 0) + return r; + + r = sd_event_now(server->event, CLOCK_REALTIME, &now_r); + if (r < 0) + return r; + + HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) { + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + + r = dhcp_server_lease_append_json(lease, &w); + if (r < 0) + return r; + + usec_t exp_r = map_clock_usec_raw(lease->expiration, now_b, now_r); + + r = json_variant_merge_objectb(&w, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("ExpirationUSec", lease->expiration), + JSON_BUILD_PAIR_UNSIGNED("ExpirationRealtimeUSec", exp_r))); + if (r < 0) + return r; + + r = json_variant_append_array(&array, w); + if (r < 0) + return r; + } + + return json_variant_set_field_non_null(v, "Leases", array); +} + +int dhcp_server_static_leases_append_json(sd_dhcp_server *server, JsonVariant **v) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + sd_dhcp_server_lease *lease; + int r; + + assert(server); + assert(v); + + HASHMAP_FOREACH(lease, server->static_leases_by_client_id) { + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + + r = dhcp_server_lease_append_json(lease, &w); + if (r < 0) + return r; + + r = json_variant_append_array(&array, w); + if (r < 0) + return r; + } + + return json_variant_set_field_non_null(v, "StaticLeases", array); +} + +int dhcp_server_save_leases(sd_dhcp_server *server) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_(unlink_and_freep) char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + sd_id128_t boot_id; + int r; + + assert(server); + + if (!server->lease_file) + return 0; + + if (hashmap_isempty(server->bound_leases_by_client_id)) { + if (unlink(server->lease_file) < 0 && errno != ENOENT) + return -errno; + + return 0; + } + + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return r; + + r = json_build(&v, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_ID128("BootID", boot_id))); + if (r < 0) + return r; + + r = dhcp_server_bound_leases_append_json(server, &v); + if (r < 0) + return r; + + r = mkdir_parents(server->lease_file, 0755); + if (r < 0) + return r; + + r = fopen_temporary(server->lease_file, &f, &temp_path); + if (r < 0) + return r; + + (void) fchmod(fileno(f), 0644); + + r = json_variant_dump(v, JSON_FORMAT_NEWLINE | JSON_FORMAT_FLUSH, f, /* prefix = */ NULL); + if (r < 0) + return r; + + r = conservative_rename(temp_path, server->lease_file); + if (r < 0) + return r; + + temp_path = mfree(temp_path); + return 0; +} + +static int json_dispatch_dhcp_lease(sd_dhcp_server *server, JsonVariant *v, bool use_boottime) { + static const JsonDispatch dispatch_table_boottime[] = { + { "ClientId", JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(sd_dhcp_server_lease, client_id), JSON_MANDATORY }, + { "Address", JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(sd_dhcp_server_lease, address), JSON_MANDATORY }, + { "Hostname", JSON_VARIANT_STRING, json_dispatch_string, offsetof(sd_dhcp_server_lease, hostname), 0 }, + { "ExpirationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(sd_dhcp_server_lease, expiration), JSON_MANDATORY }, + { "ExpirationRealtimeUSec", _JSON_VARIANT_TYPE_INVALID, NULL, 0, JSON_MANDATORY }, + {} + }, dispatch_table_realtime[] = { + { "ClientId", JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(sd_dhcp_server_lease, client_id), JSON_MANDATORY }, + { "Address", JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(sd_dhcp_server_lease, address), JSON_MANDATORY }, + { "Hostname", JSON_VARIANT_STRING, json_dispatch_string, offsetof(sd_dhcp_server_lease, hostname), 0 }, + { "ExpirationUSec", _JSON_VARIANT_TYPE_INVALID, NULL, 0, JSON_MANDATORY }, + { "ExpirationRealtimeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(sd_dhcp_server_lease, expiration), JSON_MANDATORY }, + {} + }; + + _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL; + usec_t now_b; + int r; + + assert(server); + assert(v); + + lease = new(sd_dhcp_server_lease, 1); + if (!lease) + return -ENOMEM; + + *lease = (sd_dhcp_server_lease) { + .n_ref = 1, + }; + + r = json_dispatch(v, use_boottime ? dispatch_table_boottime : dispatch_table_realtime, JSON_ALLOW_EXTENSIONS, lease); + if (r < 0) + return r; + + r = sd_event_now(server->event, CLOCK_BOOTTIME, &now_b); + if (r < 0) + return r; + + if (use_boottime) { + if (lease->expiration < now_b) + return 0; /* already expired */ + } else { + usec_t now_r; + + r = sd_event_now(server->event, CLOCK_REALTIME, &now_r); + if (r < 0) + return r; + + if (lease->expiration < now_r) + return 0; /* already expired */ + + lease->expiration = map_clock_usec_raw(lease->expiration, now_r, now_b); + } + + r = dhcp_server_put_lease(server, lease, /* is_static = */ false); + if (r == -EEXIST) + return 0; + if (r < 0) + return r; + + TAKE_PTR(lease); + return 0; +} + +typedef struct SavedInfo { + sd_id128_t boot_id; + JsonVariant *leases; +} SavedInfo; + +static int dhcp_server_dispatch_leases(sd_dhcp_server *server, JsonVariant *v) { + static const JsonDispatch dispatch_table[] = { + { "BootID", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(SavedInfo, boot_id), JSON_MANDATORY }, + { "Leases", JSON_VARIANT_ARRAY, json_dispatch_variant_noref, offsetof(SavedInfo, leases), JSON_MANDATORY }, + {} + }; + + SavedInfo info = {}; + sd_id128_t boot_id; + int r; + + r = json_dispatch(v, dispatch_table, JSON_ALLOW_EXTENSIONS, &info); + if (r < 0) + return r; + + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return r; + + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, info.leases) + RET_GATHER(r, json_dispatch_dhcp_lease(server, i, /* use_boottime = */ sd_id128_equal(info.boot_id, boot_id))); + + return r; +} + +int dhcp_server_load_leases(sd_dhcp_server *server) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + size_t n, m; + int r; + + assert(server); + assert(server->event); + + if (!server->lease_file) + return 0; + + r = json_parse_file( + /* f = */ NULL, + server->lease_file, + /* flags = */ 0, + &v, + /* ret_line = */ NULL, + /* ret_column = */ NULL); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + n = hashmap_size(server->bound_leases_by_client_id); + + r = dhcp_server_dispatch_leases(server, v); + + m = hashmap_size(server->bound_leases_by_client_id); + assert(m >= n); + log_dhcp_server(server, "Loaded %zu lease(s) from %s.", m - n, server->lease_file); + + return r; +} diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index d59bf430d7..bbe96be834 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -22,6 +22,7 @@ #include "memory-util.h" #include "network-common.h" #include "ordered-set.h" +#include "path-util.h" #include "siphash24.h" #include "string-util.h" #include "unaligned.h" @@ -30,6 +31,19 @@ #define DHCP_DEFAULT_LEASE_TIME_USEC USEC_PER_HOUR #define DHCP_MAX_LEASE_TIME_USEC (USEC_PER_HOUR*12) +static void server_on_lease_change(sd_dhcp_server *server) { + int r; + + assert(server); + + r = dhcp_server_save_leases(server); + if (r < 0) + log_dhcp_server_errno(server, r, "Failed to save leases, ignoring: %m"); + + if (server->callback) + server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata); +} + /* configures the server's address and subnet, and optionally the pool's size and offset into the subnet * the whole pool must fit into the subnet, and may not contain the first (any) nor last (broadcast) address * moreover, the server's own address may be in the pool, and is in that case reserved in order not to @@ -130,6 +144,8 @@ static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) { free(server->agent_circuit_id); free(server->agent_remote_id); + free(server->lease_file); + free(server->ifname); return mfree(server); } @@ -746,14 +762,16 @@ static int parse_request(uint8_t code, uint8_t len, const void *option, void *us req->agent_info_option = (uint8_t*)option - 2; break; - case SD_DHCP_OPTION_HOST_NAME: - r = dhcp_option_parse_string(option, len, &req->hostname); - if (r < 0) { - log_debug_errno(r, "Failed to parse hostname, ignoring: %m"); - return 0; - } + case SD_DHCP_OPTION_HOST_NAME: { + _cleanup_free_ char *p = NULL; + r = dhcp_option_parse_hostname(option, len, &p); + if (r < 0) + log_debug_errno(r, "Failed to parse hostname, ignoring: %m"); + else + free_and_replace(req->hostname, p); break; + } case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST: req->parameter_request_list = option; req->parameter_request_list_len = len; @@ -979,8 +997,7 @@ static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, be32_t a log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->xid)); - if (server->callback) - server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata); + server_on_lease_change(server); return DHCP_ACK; } @@ -1186,8 +1203,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz sd_dhcp_server_lease_unref(existing_lease); - if (server->callback) - server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata); + server_on_lease_change(server); return 0; }} @@ -1341,6 +1357,10 @@ int sd_dhcp_server_start(sd_dhcp_server *server) { goto on_error; } + r = dhcp_server_load_leases(server); + if (r < 0) + log_dhcp_server_errno(server, r, "Failed to load lease file %s, ignoring: %m", strna(server->lease_file)); + log_dhcp_server(server, "STARTED"); return 0; @@ -1565,3 +1585,13 @@ int sd_dhcp_server_set_relay_agent_information( free_and_replace(server->agent_remote_id, remote_id_dup); return 0; } + +int sd_dhcp_server_set_lease_file(sd_dhcp_server *server, const char *path) { + assert_return(server, -EINVAL); + assert_return(!sd_dhcp_server_is_running(server), -EBUSY); + + if (path && !path_is_safe(path)) + return -EINVAL; + + return free_and_strdup(&server->lease_file, path); +} diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 686d83b06b..88e5b5ec9e 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -2929,6 +2929,24 @@ static int verb_reconfigure(int argc, char *argv[], void *userdata) { return 0; } +static int verb_persistent_storage(int argc, char *argv[], void *userdata) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + bool ready; + int r; + + r = parse_boolean(argv[1]); + if (r < 0) + return log_error_errno(r, "Failed to parse argument: %s", argv[1]); + ready = r; + + r = varlink_connect_networkd(&vl); + if (r < 0) + return r; + + return varlink_callb_and_log(vl, "io.systemd.Network.SetPersistentStorage", /* reply = */ NULL, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_BOOLEAN("Ready", ready))); +} + static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -2955,6 +2973,8 @@ static int help(void) { " cat FILES|DEVICES... Show network configuration files\n" " mask FILES... Mask network configuration files\n" " unmask FILES... Unmask network configuration files\n" + " persistent-storage BOOL\n" + " Notify systemd-networkd if persistent storage is ready\n" "\nOptions:\n" " -h --help Show this help\n" " --version Show package version\n" @@ -3098,21 +3118,22 @@ static int parse_argv(int argc, char *argv[]) { static int networkctl_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, list_links }, - { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, link_status }, - { "lldp", VERB_ANY, VERB_ANY, 0, link_lldp_status }, - { "label", 1, 1, 0, list_address_labels }, - { "delete", 2, VERB_ANY, 0, link_delete }, - { "up", 2, VERB_ANY, 0, link_up_down }, - { "down", 2, VERB_ANY, 0, link_up_down }, - { "renew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_renew }, - { "forcerenew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_force_renew }, - { "reconfigure", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_reconfigure }, - { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, - { "edit", 2, VERB_ANY, 0, verb_edit }, - { "cat", 2, VERB_ANY, 0, verb_cat }, - { "mask", 2, VERB_ANY, 0, verb_mask }, - { "unmask", 2, VERB_ANY, 0, verb_unmask }, + { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, list_links }, + { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, link_status }, + { "lldp", VERB_ANY, VERB_ANY, 0, link_lldp_status }, + { "label", 1, 1, 0, list_address_labels }, + { "delete", 2, VERB_ANY, 0, link_delete }, + { "up", 2, VERB_ANY, 0, link_up_down }, + { "down", 2, VERB_ANY, 0, link_up_down }, + { "renew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_renew }, + { "forcerenew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_force_renew }, + { "reconfigure", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_reconfigure }, + { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, + { "edit", 2, VERB_ANY, 0, verb_edit }, + { "cat", 2, VERB_ANY, 0, verb_cat }, + { "mask", 2, VERB_ANY, 0, verb_mask }, + { "unmask", 2, VERB_ANY, 0, verb_unmask }, + { "persistent-storage", 2, 2, 0, verb_persistent_storage }, {} }; diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c index 81ef6d80f7..57019400e6 100644 --- a/src/network/networkd-dhcp-server.c +++ b/src/network/networkd-dhcp-server.c @@ -20,6 +20,7 @@ #include "networkd-queue.h" #include "networkd-route-util.h" #include "parse-util.h" +#include "path-util.h" #include "socket-netlink.h" #include "string-table.h" #include "string-util.h" @@ -143,6 +144,55 @@ int network_adjust_dhcp_server(Network *network, Set **addresses) { return 0; } +static int link_start_dhcp4_server(Link *link) { + int r; + + assert(link); + assert(link->manager); + + if (!link->dhcp_server) + return 0; /* Not configured yet. */ + + if (!link_has_carrier(link)) + return 0; + + /* TODO: Maybe, also check the system time is synced. If the system does not have RTC battery, then + * the realtime clock in not usable in the early boot stage, and all saved leases may be wrongly + * handled as expired and dropped. */ + if (!sd_dhcp_server_is_in_relay_mode(link->dhcp_server) && + !link->manager->persistent_storage_is_ready) + return 0; + + r = sd_dhcp_server_start(link->dhcp_server); + if (r < 0) + return r; + + log_link_debug(link, "Offering DHCPv4 leases"); + return 0; +} + +void manager_toggle_dhcp4_server_state(Manager *manager, bool start) { + Link *link; + int r; + + assert(manager); + + HASHMAP_FOREACH(link, manager->links_by_index) { + if (!link->dhcp_server) + continue; + if (sd_dhcp_server_is_in_relay_mode(link->dhcp_server)) + continue; + + if (start) + r = link_start_dhcp4_server(link); + else + r = sd_dhcp_server_stop(link->dhcp_server); + if (r < 0) + log_link_debug_errno(link, r, "Failed to %s DHCP server, ignoring: %m", + start ? "start" : "stop"); + } +} + static int dhcp_server_find_uplink(Link *link, Link **ret) { assert(link); @@ -522,11 +572,20 @@ static int dhcp4_server_configure(Link *link) { return log_link_error_errno(link, r, "Failed to set DHCPv4 static lease for DHCP server: %m"); } - r = sd_dhcp_server_start(link->dhcp_server); + if (!sd_dhcp_server_is_in_relay_mode(link->dhcp_server)) { + _cleanup_free_ char *lease_file = path_join("/var/lib/systemd/network/dhcp-server-lease/", link->ifname); + if (!lease_file) + return log_oom(); + + r = sd_dhcp_server_set_lease_file(link->dhcp_server, lease_file); + if (r < 0) + log_link_warning_errno(link, r, "Failed to load DHCPv4 server leases, ignoring: %m"); + } + + r = link_start_dhcp4_server(link); if (r < 0) return log_link_error_errno(link, r, "Could not start DHCPv4 server instance: %m"); - log_link_debug(link, "Offering DHCPv4 leases"); return 0; } diff --git a/src/network/networkd-dhcp-server.h b/src/network/networkd-dhcp-server.h index 960232ade6..b845a6d1c7 100644 --- a/src/network/networkd-dhcp-server.h +++ b/src/network/networkd-dhcp-server.h @@ -5,12 +5,15 @@ #include "set.h" typedef struct Link Link; +typedef struct Manager Manager; typedef struct Network Network; int network_adjust_dhcp_server(Network *network, Set **addresses); int link_request_dhcp_server(Link *link); +void manager_toggle_dhcp4_server_state(Manager *manager, bool start); + CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_relay_agent_suboption); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_emit); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_address); diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index 57bea190f5..88df71b053 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -890,67 +890,6 @@ static int pref64_append_json(Link *link, JsonVariant **v) { return json_variant_set_field_non_null(v, "NDisc", w); } -static int dhcp_server_offered_leases_append_json(Link *link, JsonVariant **v) { - _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; - sd_dhcp_server_lease *lease; - int r; - - assert(link); - assert(v); - - if (!link->dhcp_server) - return 0; - - HASHMAP_FOREACH(lease, link->dhcp_server->bound_leases_by_client_id) { - struct in_addr address = { .s_addr = lease->address }; - - r = json_variant_append_arrayb( - &array, - JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_BYTE_ARRAY( - "ClientId", - lease->client_id.raw, - lease->client_id.size), - JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("Address", &address), - JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", lease->hostname), - JSON_BUILD_PAIR_FINITE_USEC( - "ExpirationUSec", lease->expiration))); - if (r < 0) - return r; - } - - return json_variant_set_field_non_null(v, "Leases", array); -} - -static int dhcp_server_static_leases_append_json(Link *link, JsonVariant **v) { - _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; - sd_dhcp_server_lease *lease; - int r; - - assert(link); - assert(v); - - if (!link->dhcp_server) - return 0; - - HASHMAP_FOREACH(lease, link->dhcp_server->static_leases_by_client_id) { - struct in_addr address = { .s_addr = lease->address }; - - r = json_variant_append_arrayb( - &array, - JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_BYTE_ARRAY( - "ClientId", - lease->client_id.raw, - lease->client_id.size), - JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("Address", &address))); - if (r < 0) - return r; - } - - return json_variant_set_field_non_null(v, "StaticLeases", array); -} - static int dhcp_server_append_json(Link *link, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; int r; @@ -968,11 +907,11 @@ static int dhcp_server_append_json(Link *link, JsonVariant **v) { if (r < 0) return r; - r = dhcp_server_offered_leases_append_json(link, &w); + r = dhcp_server_bound_leases_append_json(link->dhcp_server, &w); if (r < 0) return r; - r = dhcp_server_static_leases_append_json(link, &w); + r = dhcp_server_static_leases_append_json(link->dhcp_server, &w); if (r < 0) return r; diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c index 0c54e86aea..f3b265fed5 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -2,8 +2,12 @@ #include +#include "bus-polkit.h" +#include "fs-util.h" #include "lldp-rx-internal.h" +#include "networkd-dhcp-server.h" #include "networkd-manager-varlink.h" +#include "stat-util.h" #include "varlink.h" #include "varlink-io.systemd.Network.h" @@ -164,6 +168,55 @@ static int vl_method_get_lldp_neighbors(Varlink *vlink, JsonVariant *parameters, JSON_BUILD_PAIR_CONDITION(!json_variant_is_blank_array(array), "Neighbors", JSON_BUILD_VARIANT(array)))); } +static int vl_method_set_persistent_storage(Varlink *vlink, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + { "Ready", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, 0, 0 }, + {} + }; + + Manager *manager = ASSERT_PTR(userdata); + bool ready; + int r; + + assert(vlink); + + r = varlink_dispatch(vlink, parameters, dispatch_table, &ready); + if (r != 0) + return r; + + if (ready) { + r = path_is_read_only_fs("/var/lib/systemd/network/"); + if (r < 0) + return log_warning_errno(r, "Failed to check if /var/lib/systemd/network/ is writable: %m"); + if (r > 0) + return log_warning_errno(SYNTHETIC_ERRNO(EROFS), "The directory /var/lib/systemd/network/ is read-only."); + } + + r = varlink_verify_polkit_async( + vlink, + manager->bus, + "org.freedesktop.network1.set-persistent-storage", + /* details= */ NULL, + &manager->polkit_registry); + if (r <= 0) + return r; + + manager->persistent_storage_is_ready = ready; + + if (ready) { + r = touch("/run/systemd/netif/persistent-storage-ready"); + if (r < 0) + log_debug_errno(r, "Failed to create /run/systemd/netif/persistent-storage-ready, ignoring: %m"); + } else { + if (unlink("/run/systemd/netif/persistent-storage-ready") < 0 && errno != ENOENT) + log_debug_errno(errno, "Failed to remove /run/systemd/netif/persistent-storage-ready, ignoring: %m"); + } + + manager_toggle_dhcp4_server_state(manager, ready); + + return varlink_reply(vlink, NULL); +} + int manager_connect_varlink(Manager *m) { _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; int r; @@ -187,7 +240,8 @@ int manager_connect_varlink(Manager *m) { s, "io.systemd.Network.GetStates", vl_method_get_states, "io.systemd.Network.GetNamespaceId", vl_method_get_namespace_id, - "io.systemd.Network.GetLLDPNeighbors", vl_method_get_lldp_neighbors); + "io.systemd.Network.GetLLDPNeighbors", vl_method_get_lldp_neighbors, + "io.systemd.Network.SetPersistentStorage", vl_method_set_persistent_storage); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index c0d368ab11..b955eb9447 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -557,6 +557,29 @@ int manager_setup(Manager *m) { return 0; } +static bool persistent_storage_is_ready(void) { + int r; + + if (access("/run/systemd/netif/persistent-storage-ready", F_OK) < 0) { + if (errno != ENOENT) + return log_debug_errno(errno, "Failed to check if /run/systemd/netif/persistent-storage-ready exists, assuming not, ignoring: %m"); + return false; + } + + r = path_is_read_only_fs("/var/lib/systemd/network/"); + if (r == 0) + return true; + if (r < 0) + log_debug_errno(r, "Failed to check if /var/lib/systemd/network/ is writable: %m"); + else + log_debug("The directory /var/lib/systemd/network/ is read-only."); + + if (unlink("/run/systemd/netif/persistent-storage-ready") < 0 && errno != ENOENT) + log_debug_errno(errno, "Failed to remove /run/systemd/netif/persistent-storage-ready, ignoring: %m"); + + return false; +} + int manager_new(Manager **ret, bool test_mode) { _cleanup_(manager_freep) Manager *m = NULL; @@ -568,6 +591,7 @@ int manager_new(Manager **ret, bool test_mode) { .keep_configuration = _KEEP_CONFIGURATION_INVALID, .ipv6_privacy_extensions = IPV6_PRIVACY_EXTENSIONS_NO, .test_mode = test_mode, + .persistent_storage_is_ready = persistent_storage_is_ready(), .speed_meter_interval_usec = SPEED_METER_DEFAULT_TIME_INTERVAL, .online_state = _LINK_ONLINE_STATE_INVALID, .manage_foreign_routes = true, diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index 8088e20b38..320b859bfd 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -41,6 +41,7 @@ struct Manager { bool manage_foreign_routes; bool manage_foreign_rules; bool manage_foreign_nexthops; + bool persistent_storage_is_ready; Set *dirty_links; Set *new_wlan_ifindices; diff --git a/src/network/org.freedesktop.network1.policy b/src/network/org.freedesktop.network1.policy index 1e2d8d7cb4..e2525508a4 100644 --- a/src/network/org.freedesktop.network1.policy +++ b/src/network/org.freedesktop.network1.policy @@ -183,4 +183,15 @@ unix-user:systemd-network + + Specify whether persistent storage for systemd-networkd is available. + Authentication is required to specify whether persistent storage for systemd-networkd is available. + + auth_admin + auth_admin + auth_admin_keep + + unix-user:systemd-network + + diff --git a/src/shared/varlink-io.systemd.Network.c b/src/shared/varlink-io.systemd.Network.c index 986f2cfc69..c3be38b99a 100644 --- a/src/shared/varlink-io.systemd.Network.c +++ b/src/shared/varlink-io.systemd.Network.c @@ -40,11 +40,16 @@ static VARLINK_DEFINE_METHOD( VARLINK_DEFINE_INPUT(InterfaceName, VARLINK_STRING, VARLINK_NULLABLE), VARLINK_DEFINE_OUTPUT_BY_TYPE(Neighbors, LLDPNeighborsByInterface, VARLINK_ARRAY)); +static VARLINK_DEFINE_METHOD( + SetPersistentStorage, + VARLINK_DEFINE_INPUT(Ready, VARLINK_BOOL, 0)); + VARLINK_DEFINE_INTERFACE( io_systemd_Network, "io.systemd.Network", &vl_method_GetStates, &vl_method_GetNamespaceId, &vl_method_GetLLDPNeighbors, + &vl_method_SetPersistentStorage, &vl_type_LLDPNeighbor, &vl_type_LLDPNeighborsByInterface); diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h index feafa5d1fc..486af48b25 100644 --- a/src/systemd/sd-dhcp-server.h +++ b/src/systemd/sd-dhcp-server.h @@ -81,6 +81,7 @@ int sd_dhcp_server_set_smtp(sd_dhcp_server *server, const struct in_addr smtp[], int sd_dhcp_server_add_option(sd_dhcp_server *server, sd_dhcp_option *v); int sd_dhcp_server_add_vendor_option(sd_dhcp_server *server, sd_dhcp_option *v); int sd_dhcp_server_set_static_lease(sd_dhcp_server *server, const struct in_addr *address, uint8_t *client_id, size_t client_id_size); +int sd_dhcp_server_set_lease_file(sd_dhcp_server *server, const char *path); int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint64_t t); int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint64_t t); diff --git a/test/networkd-test.py b/test/networkd-test.py index 512137cacc..ab30928c27 100755 --- a/test/networkd-test.py +++ b/test/networkd-test.py @@ -888,8 +888,10 @@ class NetworkdClientTest(ClientTestBase, unittest.TestCase): set -eu mkdir -p /run/systemd/network mkdir -p /run/systemd/netif +mkdir -p /var/lib/systemd/network mount -t tmpfs none /run/systemd/network mount -t tmpfs none /run/systemd/netif +mount -t tmpfs none /var/lib/systemd/network [ ! -e /run/dbus ] || mount -t tmpfs none /run/dbus # create router/client veth pair cat </run/systemd/network/50-test.netdev @@ -917,6 +919,10 @@ DNS=192.168.5.1 {dhopts} EOF +# For the networkd instance invoked below cannot support varlink connection. +# Hence, 'networkctl persistent-storage yes' cannot be used. +touch /run/systemd/netif/persistent-storage-ready + # run networkd as in systemd-networkd.service exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ {{ s/^.*=//; s/^[@+-]//; s/^!*//; p}}') '''.format(ifr=self.if_router, @@ -930,6 +936,7 @@ exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ {{ s/^.*=/ '-p', 'InaccessibleDirectories=-/etc/systemd/network', '-p', 'InaccessibleDirectories=-/run/systemd/network', '-p', 'InaccessibleDirectories=-/run/systemd/netif', + '-p', 'InaccessibleDirectories=-/var/lib/systemd/network', '--service-type=notify', script]) # wait until devices got created diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 37dc69aafd..1bf79d974c 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -51,18 +51,17 @@ radvd_pid_file = '/run/networkd-ci/test-radvd.pid' systemd_lib_paths = ['/usr/lib/systemd', '/lib/systemd'] which_paths = ':'.join(systemd_lib_paths + os.getenv('PATH', os.defpath).lstrip(':').split(':')) -systemd_source_dir = None networkd_bin = shutil.which('systemd-networkd', path=which_paths) resolved_bin = shutil.which('systemd-resolved', path=which_paths) timesyncd_bin = shutil.which('systemd-timesyncd', path=which_paths) -udevd_bin = shutil.which('systemd-udevd', path=which_paths) wait_online_bin = shutil.which('systemd-networkd-wait-online', path=which_paths) networkctl_bin = shutil.which('networkctl', path=which_paths) resolvectl_bin = shutil.which('resolvectl', path=which_paths) timedatectl_bin = shutil.which('timedatectl', path=which_paths) udevadm_bin = shutil.which('udevadm', path=which_paths) -systemd_udev_rules_build_dir = None +build_dir = None +source_dir = None use_valgrind = False valgrind_cmd = '' @@ -365,12 +364,13 @@ def clear_networkd_conf_dropins(): rm_rf(networkd_conf_dropin_dir) def setup_systemd_udev_rules(): - if not systemd_udev_rules_build_dir: + if not build_dir: return mkdir_p(udev_rules_dir) - for path in [systemd_udev_rules_build_dir, os.path.join(systemd_source_dir, "rules.d")]: + for path in [build_dir, source_dir]: + path = os.path.join(path, "rules.d") print(f"Copying udev rules from {path} to {udev_rules_dir}") for rule in os.listdir(path): @@ -378,6 +378,9 @@ def setup_systemd_udev_rules(): continue cp(os.path.join(path, rule), udev_rules_dir) +def clear_networkd_state_files(): + rm_rf('/var/lib/systemd/network/') + def copy_udev_rule(*rules): """Copy udev rules""" mkdir_p(udev_rules_dir) @@ -412,11 +415,12 @@ def create_unit_dropin(unit, contents): f.write('\n'.join(contents)) def create_service_dropin(service, command, additional_settings=None): - drop_in = [ - '[Service]', - 'ExecStart=', - f'ExecStart=!!{valgrind_cmd}{command}', - ] + drop_in = ['[Service]'] + if command: + drop_in += [ + 'ExecStart=', + f'ExecStart=!!{valgrind_cmd}{command}', + ] if enable_debug: drop_in += ['Environment=SYSTEMD_LOG_LEVEL=debug'] if asan_options: @@ -444,6 +448,87 @@ def create_service_dropin(service, command, additional_settings=None): create_unit_dropin(f'{service}.service', drop_in) +def setup_system_units(): + if build_dir: + mkdir_p('/run/systemd/system/') + + for unit in [ + 'systemd-networkd.service', + 'systemd-networkd.socket', + 'systemd-networkd-persistent-storage.service', + 'systemd-resolved.service', + 'systemd-timesyncd.service', + 'systemd-udevd.service', + ]: + for path in [build_dir, source_dir]: + fullpath = os.path.join(os.path.join(path, "units"), unit) + if os.path.exists(fullpath): + print(f"Copying unit file from {fullpath} to /run/systemd/system/") + cp(fullpath, '/run/systemd/system/') + break + + create_service_dropin('systemd-networkd', networkd_bin, + ['[Service]', + 'Restart=no', + 'Environment=SYSTEMD_NETWORK_TEST_MODE=yes', + '[Unit]', + 'StartLimitIntervalSec=0']) + create_service_dropin('systemd-resolved', resolved_bin) + create_service_dropin('systemd-timesyncd', timesyncd_bin) + + # TODO: also run udevd with sanitizers, valgrind, or coverage + create_unit_dropin( + 'systemd-udevd.service', + [ + '[Service]', + 'ExecStart=', + f'ExecStart=!!@{udevadm_bin} systemd-udevd', + ] + ) + create_unit_dropin( + 'systemd-networkd.socket', + [ + '[Unit]', + 'StartLimitIntervalSec=0', + ] + ) + create_unit_dropin( + 'systemd-networkd-persistent-storage.service', + [ + '[Unit]', + 'StartLimitIntervalSec=0', + '[Service]', + 'ExecStart=', + f'ExecStart={networkctl_bin} persistent-storage yes', + 'ExecStop=', + f'ExecStop={networkctl_bin} persistent-storage no' + ] + ) + + check_output('systemctl daemon-reload') + print(check_output('systemctl cat systemd-networkd.service')) + print(check_output('systemctl cat systemd-networkd-persistent-storage.service')) + print(check_output('systemctl cat systemd-resolved.service')) + print(check_output('systemctl cat systemd-timesyncd.service')) + print(check_output('systemctl cat systemd-udevd.service')) + check_output('systemctl restart systemd-resolved.service') + check_output('systemctl restart systemd-timesyncd.service') + check_output('systemctl restart systemd-udevd.service') + +def clear_system_units(): + def rm_unit(name): + rm_f(f'/run/systemd/system/{name}') + rm_rf(f'/run/systemd/system/{name}.d') + + rm_unit('systemd-networkd.service') + rm_unit('systemd-networkd.socket') + rm_unit('systemd-networkd-persistent-storage.service') + rm_unit('systemd-resolved.service') + rm_unit('systemd-timesyncd.service') + rm_unit('systemd-udevd.service') + check_output('systemctl daemon-reload') + check_output('systemctl restart systemd-udevd.service') + def link_exists(link): return call_quiet(f'ip link show {link}') == 0 @@ -830,6 +915,7 @@ def tear_down_common(): # 6. remove configs clear_network_units() clear_networkd_conf_dropins() + clear_networkd_state_files() # 7. flush settings flush_fou_ports() @@ -843,6 +929,7 @@ def setUpModule(): clear_network_units() clear_networkd_conf_dropins() + clear_networkd_state_files() clear_udev_rules() setup_systemd_udev_rules() @@ -855,58 +942,18 @@ def setUpModule(): save_routing_policy_rules() save_timezone() - create_service_dropin('systemd-networkd', networkd_bin, - ['[Service]', - 'Restart=no', - 'Environment=SYSTEMD_NETWORK_TEST_MODE=yes', - '[Unit]', - 'StartLimitIntervalSec=0']) - create_service_dropin('systemd-resolved', resolved_bin) - create_service_dropin('systemd-timesyncd', timesyncd_bin) - - # TODO: also run udevd with sanitizers, valgrind, or coverage - #create_service_dropin('systemd-udevd', udevd_bin, - # f'{udevadm_bin} control --reload --timeout 0') - create_unit_dropin( - 'systemd-udevd.service', - [ - '[Service]', - 'ExecStart=', - f'ExecStart=!!@{udevd_bin} systemd-udevd', - ] - ) - create_unit_dropin( - 'systemd-networkd.socket', - [ - '[Unit]', - 'StartLimitIntervalSec=0', - ] - ) - - check_output('systemctl daemon-reload') - print(check_output('systemctl cat systemd-networkd.service')) - print(check_output('systemctl cat systemd-resolved.service')) - print(check_output('systemctl cat systemd-timesyncd.service')) - print(check_output('systemctl cat systemd-udevd.service')) - check_output('systemctl restart systemd-resolved.service') - check_output('systemctl restart systemd-timesyncd.service') - check_output('systemctl restart systemd-udevd.service') + setup_system_units() def tearDownModule(): rm_rf(networkd_ci_temp_dir) clear_udev_rules() clear_network_units() clear_networkd_conf_dropins() + clear_networkd_state_files() restore_timezone() - rm_rf('/run/systemd/system/systemd-networkd.service.d') - rm_rf('/run/systemd/system/systemd-networkd.socket.d') - rm_rf('/run/systemd/system/systemd-resolved.service.d') - rm_rf('/run/systemd/system/systemd-timesyncd.service.d') - rm_rf('/run/systemd/system/systemd-udevd.service.d') - check_output('systemctl daemon-reload') - check_output('systemctl restart systemd-udevd.service') + clear_system_units() restore_active_units() class Utilities(): @@ -5687,8 +5734,20 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities): self.assertRegex(output, 'NTP: 192.168.5.1\n *192.168.5.11') output = networkctl_status('veth-peer') + print(output) self.assertRegex(output, "Offered DHCP leases: 192.168.5.[0-9]*") + networkctl_reconfigure('veth-peer') + self.wait_online('veth-peer:routable') + + for _ in range(10): + output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth-peer', env=env) + if 'Offered DHCP leases: 192.168.5.' in output: + break + time.sleep(.2) + else: + self.fail() + def test_dhcp_server_null_server_address(self): copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server-null-server-address.network') start_networkd() @@ -7464,15 +7523,6 @@ if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--build-dir', help='Path to build dir', dest='build_dir') parser.add_argument('--source-dir', help='Path to source dir/git tree', dest='source_dir') - parser.add_argument('--networkd', help='Path to systemd-networkd', dest='networkd_bin') - parser.add_argument('--resolved', help='Path to systemd-resolved', dest='resolved_bin') - parser.add_argument('--timesyncd', help='Path to systemd-timesyncd', dest='timesyncd_bin') - parser.add_argument('--udevd', help='Path to systemd-udevd', dest='udevd_bin') - parser.add_argument('--wait-online', help='Path to systemd-networkd-wait-online', dest='wait_online_bin') - parser.add_argument('--networkctl', help='Path to networkctl', dest='networkctl_bin') - parser.add_argument('--resolvectl', help='Path to resolvectl', dest='resolvectl_bin') - parser.add_argument('--timedatectl', help='Path to timedatectl', dest='timedatectl_bin') - parser.add_argument('--udevadm', help='Path to udevadm', dest='udevadm_bin') parser.add_argument('--valgrind', help='Enable valgrind', dest='use_valgrind', type=bool, nargs='?', const=True, default=use_valgrind) parser.add_argument('--debug', help='Generate debugging logs', dest='enable_debug', type=bool, nargs='?', const=True, default=enable_debug) parser.add_argument('--asan-options', help='ASAN options', dest='asan_options') @@ -7482,45 +7532,21 @@ if __name__ == '__main__': ns, unknown_args = parser.parse_known_args(namespace=unittest) if ns.build_dir: - if ns.networkd_bin or ns.resolved_bin or ns.timesyncd_bin or ns.udevd_bin or \ - ns.wait_online_bin or ns.networkctl_bin or ns.resolvectl_bin or ns.timedatectl_bin or ns.udevadm_bin: - print('WARNING: --networkd, --resolved, --timesyncd, --udevd, --wait-online, --networkctl, --resolvectl, --timedatectl, or --udevadm options are ignored when --build-dir is specified.') networkd_bin = os.path.join(ns.build_dir, 'systemd-networkd') resolved_bin = os.path.join(ns.build_dir, 'systemd-resolved') timesyncd_bin = os.path.join(ns.build_dir, 'systemd-timesyncd') - udevd_bin = os.path.join(ns.build_dir, 'udevadm') wait_online_bin = os.path.join(ns.build_dir, 'systemd-networkd-wait-online') networkctl_bin = os.path.join(ns.build_dir, 'networkctl') resolvectl_bin = os.path.join(ns.build_dir, 'resolvectl') timedatectl_bin = os.path.join(ns.build_dir, 'timedatectl') udevadm_bin = os.path.join(ns.build_dir, 'udevadm') - systemd_udev_rules_build_dir = os.path.join(ns.build_dir, 'rules.d') - else: - if ns.networkd_bin: - networkd_bin = ns.networkd_bin - if ns.resolved_bin: - resolved_bin = ns.resolved_bin - if ns.timesyncd_bin: - timesyncd_bin = ns.timesyncd_bin - if ns.udevd_bin: - udevd_bin = ns.udevd_bin - if ns.wait_online_bin: - wait_online_bin = ns.wait_online_bin - if ns.networkctl_bin: - networkctl_bin = ns.networkctl_bin - if ns.resolvectl_bin: - resolvectl_bin = ns.resolvectl_bin - if ns.timedatectl_bin: - timedatectl_bin = ns.timedatectl_bin - if ns.udevadm_bin: - udevadm_bin = ns.udevadm_bin + build_dir = ns.build_dir if ns.source_dir: - systemd_source_dir = ns.source_dir + source_dir = ns.source_dir else: - systemd_source_dir = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) - if not os.path.exists(os.path.join(systemd_source_dir, "meson_options.txt")): - raise RuntimeError(f"{systemd_source_dir} doesn't appear to be a systemd source tree") + source_dir = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) + assert os.path.exists(os.path.join(source_dir, "meson_options.txt")), f"{source_dir} doesn't appear to be a systemd source tree." use_valgrind = ns.use_valgrind enable_debug = ns.enable_debug diff --git a/tmpfiles.d/systemd-network.conf b/tmpfiles.d/systemd-network.conf index 24197555ee..323beca59c 100644 --- a/tmpfiles.d/systemd-network.conf +++ b/tmpfiles.d/systemd-network.conf @@ -7,6 +7,7 @@ # See tmpfiles.d(5) for details -d /run/systemd/netif 0755 systemd-network systemd-network - -d /run/systemd/netif/links 0755 systemd-network systemd-network - +d /run/systemd/netif 0755 systemd-network systemd-network - +d /run/systemd/netif/links 0755 systemd-network systemd-network - d /run/systemd/netif/leases 0755 systemd-network systemd-network - +d /var/lib/systemd/network 0755 systemd-network systemd-network - diff --git a/units/meson.build b/units/meson.build index afe9ec7d1d..1458cc5986 100644 --- a/units/meson.build +++ b/units/meson.build @@ -432,6 +432,10 @@ units = [ 'symlinks' : ['sysinit.target.wants/'], }, { 'file' : 'systemd-network-generator.service.in' }, + { + 'file' : 'systemd-networkd-persistent-storage.service', + 'conditions' : ['ENABLE_NETWORKD'], + }, { 'file' : 'systemd-networkd-wait-online.service.in', 'conditions' : ['ENABLE_NETWORKD'], diff --git a/units/systemd-networkd-persistent-storage.service b/units/systemd-networkd-persistent-storage.service new file mode 100644 index 0000000000..c151d36285 --- /dev/null +++ b/units/systemd-networkd-persistent-storage.service @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# 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. + +[Unit] +Description=Enable Persistent Storage in systemd-networkd +Documentation=man:networkctl(1) +ConditionCapability=CAP_NET_ADMIN +DefaultDependencies=no +After=systemd-remount-fs.service systemd-networkd.service +BindsTo=systemd-networkd.service +Conflicts=shutdown.target +RequiresMountsFor=/var/lib/systemd/network +ConditionPathExists=!/etc/initrd-release + +[Service] +Type=oneshot +User=systemd-network +ExecStart=networkctl persistent-storage yes +ExecStop=networkctl persistent-storage no +StateDirectory=systemd/network +RemainAfterExit=yes diff --git a/units/systemd-networkd.service.in b/units/systemd-networkd.service.in index 32b6e9fa2f..200b126faa 100644 --- a/units/systemd-networkd.service.in +++ b/units/systemd-networkd.service.in @@ -17,7 +17,7 @@ DefaultDependencies=no After=systemd-networkd.socket systemd-udevd.service network-pre.target systemd-sysusers.service systemd-sysctl.service Before=network.target multi-user.target shutdown.target initrd-switch-root.target Conflicts=shutdown.target initrd-switch-root.target -Wants=systemd-networkd.socket network.target +Wants=systemd-networkd.socket network.target systemd-networkd-persistent-storage.service [Service] AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW @@ -35,7 +35,7 @@ ProtectControlGroups=yes ProtectHome=yes ProtectKernelLogs=yes ProtectKernelModules=yes -ProtectSystem=strict +ProtectSystem=full Restart=on-failure RestartKillSignal=SIGUSR2 RestartSec=0