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