From f66045c7d4a0b67de3b3f41777195ecd26f1cf74 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 1 Mar 2024 13:21:20 +0900 Subject: [PATCH 01/14] test-network: drop unused options and variables This also renames several variables, and replace RuntimeError with assert. --- test/test-network/systemd-networkd-tests.py | 55 ++++----------------- 1 file changed, 10 insertions(+), 45 deletions(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 37dc69aafd..74f2477a0b 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): @@ -865,14 +865,12 @@ def setUpModule(): 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', + f'ExecStart=!!@{udevadm_bin} systemd-udevd', ] ) create_unit_dropin( @@ -7464,15 +7462,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 +7471,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 From 83cc18257ecc5afa2e43de853b179a0a6a51f0f1 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 1 Mar 2024 13:38:59 +0900 Subject: [PATCH 02/14] test-network: copy system unit files from build or source directory Addresses https://github.com/systemd/systemd/pull/30021#issuecomment-1971090682. --- test/test-network/systemd-networkd-tests.py | 109 ++++++++++++-------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 74f2477a0b..c7eb06cae6 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -444,6 +444,72 @@ 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-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', + ] + ) + + 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') + +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-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 @@ -855,40 +921,7 @@ 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_unit_dropin( - 'systemd-udevd.service', - [ - '[Service]', - 'ExecStart=', - f'ExecStart=!!@{udevadm_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) @@ -898,13 +931,7 @@ def tearDownModule(): 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(): From f90eb086270f0aea8efcbff5a5e4c338d178cfd4 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 1 Mar 2024 11:21:56 +0900 Subject: [PATCH 03/14] network/varlink: introduce io.systemd.Network.SetPersistentStorage method And make the networkd use state directory. Currently, the state directory and the method are not used, but will be used later. --- src/network/networkd-manager-varlink.c | 53 ++++++++++++++++++++- src/network/networkd-manager.c | 24 ++++++++++ src/network/networkd-manager.h | 1 + src/network/org.freedesktop.network1.policy | 11 +++++ src/shared/varlink-io.systemd.Network.c | 5 ++ units/systemd-networkd.service.in | 2 +- 6 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c index 0c54e86aea..a85751491e 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -2,8 +2,11 @@ #include +#include "bus-polkit.h" +#include "fs-util.h" #include "lldp-rx-internal.h" #include "networkd-manager-varlink.h" +#include "stat-util.h" #include "varlink.h" #include "varlink-io.systemd.Network.h" @@ -164,6 +167,53 @@ 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"); + } + + return varlink_reply(vlink, NULL); +} + int manager_connect_varlink(Manager *m) { _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; int r; @@ -187,7 +237,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/units/systemd-networkd.service.in b/units/systemd-networkd.service.in index 32b6e9fa2f..3dcaa55aac 100644 --- a/units/systemd-networkd.service.in +++ b/units/systemd-networkd.service.in @@ -35,7 +35,7 @@ ProtectControlGroups=yes ProtectHome=yes ProtectKernelLogs=yes ProtectKernelModules=yes -ProtectSystem=strict +ProtectSystem=full Restart=on-failure RestartKillSignal=SIGUSR2 RestartSec=0 From 91676b645886382328c261efd5e83490d16e75cf Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 26 Feb 2024 10:33:20 +0900 Subject: [PATCH 04/14] networkctl: introduce "persistent-storage" command Then, this introduces systemd-networkd-persistent-storage.service. systemd-networkd.service is an early starting service. So, at the time it is started, the persistent storage for the service may not be ready, and we cannot use StateDirectory=systemd/network in systemd-networkd.service. The newly added systemd-networkd-persistent-storage.service creates the state directory for networkd, and notify systemd-networkd that the directory is usable. --- man/networkctl.xml | 15 ++++++ src/network/networkctl.c | 51 +++++++++++++------ tmpfiles.d/systemd-network.conf | 5 +- units/meson.build | 4 ++ ...ystemd-networkd-persistent-storage.service | 27 ++++++++++ units/systemd-networkd.service.in | 2 +- 6 files changed, 86 insertions(+), 18 deletions(-) create mode 100644 units/systemd-networkd-persistent-storage.service 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/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/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 3dcaa55aac..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 From c84a5f5eaf6a929505a76acfebfe63cdd6152de8 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 1 Mar 2024 12:09:00 +0900 Subject: [PATCH 05/14] test-network: add support for systemd-networkd-persistent-storage.service --- test/networkd-test.py | 7 +++++ test/test-network/systemd-networkd-tests.py | 32 +++++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) 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 c7eb06cae6..5200999018 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -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: @@ -451,6 +455,7 @@ def setup_system_units(): for unit in [ 'systemd-networkd.service', 'systemd-networkd.socket', + 'systemd-networkd-persistent-storage.service', 'systemd-resolved.service', 'systemd-timesyncd.service', 'systemd-udevd.service', @@ -487,9 +492,22 @@ def setup_system_units(): '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')) @@ -504,6 +522,7 @@ def clear_system_units(): 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') @@ -896,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() @@ -909,6 +929,7 @@ def setUpModule(): clear_network_units() clear_networkd_conf_dropins() + clear_networkd_state_files() clear_udev_rules() setup_systemd_udev_rules() @@ -928,6 +949,7 @@ def tearDownModule(): clear_udev_rules() clear_network_units() clear_networkd_conf_dropins() + clear_networkd_state_files() restore_timezone() From fa3357b9e8d9d7a486902d0b6d4b4015fc10aac0 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 12 Mar 2024 01:32:03 +0900 Subject: [PATCH 06/14] dhcp-option: refuse control and non-UTF8 characters in string option We oftem save parsed DHCP options into a file, or expose them through DBus or Varlink. In such case, control characters or non-UTF8 characters may cause many kind of unexpected errors. In general, a DHCP message that have string options with spurious characters is mostly malformed or broken. Let's refuse them. This also makes dhcp_option_parse_string() do not free 'ret' argument, to follow our usual coding style. So, callers now need to free the pre-exisitng string if necessary. Fixes #31708. --- src/libsystemd-network/dhcp-option.c | 28 +++++++++++++------------- src/libsystemd-network/sd-dhcp-lease.c | 10 ++++++--- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index 4025dd3632..4a6fa462f1 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -396,27 +396,27 @@ 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; } 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) From 5a2f378362f2b16f8d837aec4c44532eed737a03 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 12 Mar 2024 01:47:17 +0900 Subject: [PATCH 07/14] sd-dhcp-server: refuse invalid hostname in request Currently, the received hostname is not used for assigning an address to the host, or options in the subsequent reply message. But, the parsed hostname is exposed through DBus, and possibly Varlink in the future. Let's ignore spurious hostname. --- src/libsystemd-network/dhcp-option.c | 31 +++++++++++++++++++++++++ src/libsystemd-network/dhcp-option.h | 1 + src/libsystemd-network/sd-dhcp-server.c | 14 ++++++----- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index 4a6fa462f1..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" @@ -420,6 +422,35 @@ int dhcp_option_parse_string(const uint8_t *option, size_t len, char **ret) { 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; +} + static sd_dhcp_option* dhcp_option_free(sd_dhcp_option *i) { if (!i) return NULL; 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/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index d59bf430d7..dd4cbd3064 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -746,14 +746,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; From b923ada838b4e15802bcbccf2e4b89fc3614f289 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 3 Jan 2024 06:07:05 +0900 Subject: [PATCH 08/14] sd-dhcp-server-lease: move functions to build json format No functional change, just preparation for later commits. --- .../dhcp-server-lease-internal.h | 4 ++ src/libsystemd-network/sd-dhcp-server-lease.c | 63 ++++++++++++++++++ src/network/networkd-json.c | 65 +------------------ 3 files changed, 69 insertions(+), 63 deletions(-) diff --git a/src/libsystemd-network/dhcp-server-lease-internal.h b/src/libsystemd-network/dhcp-server-lease-internal.h index 49bc173307..82ad1b0aed 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,6 @@ 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); diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c index db1d0464ae..deec9356a1 100644 --- a/src/libsystemd-network/sd-dhcp-server-lease.c +++ b/src/libsystemd-network/sd-dhcp-server-lease.c @@ -204,3 +204,66 @@ 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; + int r; + + assert(server); + assert(v); + + 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; + + r = json_variant_merge_objectb(&w, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("ExpirationUSec", lease->expiration))); + 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); +} 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; From 3ecd6fa98be2d434d12c75e023b85f52a073443f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 6 Mar 2024 09:57:06 +0900 Subject: [PATCH 09/14] time-util: expose map_clock_usec_internal() as map_clock_usec_raw() This will be used later. --- src/basic/time-util.c | 16 ++++++++-------- src/basic/time-util.h | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) 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); From aa8200e971facc8412ebf3b24df543b7808d1ad6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 1 Mar 2024 10:37:51 +0900 Subject: [PATCH 10/14] sd-dhcp-server: also expose lease expiration timestamp in realtime Here, we use map_clock_usec_raw(), instead of map_clock_usec() to reduce number of calls of now() -> clock_gettime(). --- src/libsystemd-network/sd-dhcp-server-lease.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c index deec9356a1..1716044563 100644 --- a/src/libsystemd-network/sd-dhcp-server-lease.c +++ b/src/libsystemd-network/sd-dhcp-server-lease.c @@ -219,11 +219,20 @@ static int dhcp_server_lease_append_json(sd_dhcp_server_lease *lease, JsonVarian 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; @@ -231,9 +240,12 @@ int dhcp_server_bound_leases_append_json(sd_dhcp_server *server, JsonVariant **v 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("ExpirationUSec", lease->expiration), + JSON_BUILD_PAIR_UNSIGNED("ExpirationRealtimeUSec", exp_r))); if (r < 0) return r; From 033f1197564fdaf7b7bbcbc04f49e3363316b4bb Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 3 Jan 2024 06:07:17 +0900 Subject: [PATCH 11/14] sd-dhcp-server: introduce sd_dhcp_server_set_lease_file() and dhcp_server_{save,load}_leases(). The functionality is not used networkd yet in this commit, but will be used in later commits. --- src/libsystemd-network/dhcp-server-internal.h | 2 + .../dhcp-server-lease-internal.h | 3 + src/libsystemd-network/sd-dhcp-server-lease.c | 188 ++++++++++++++++++ src/libsystemd-network/sd-dhcp-server.c | 36 +++- src/systemd/sd-dhcp-server.h | 1 + 5 files changed, 226 insertions(+), 4 deletions(-) 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 82ad1b0aed..518c151094 100644 --- a/src/libsystemd-network/dhcp-server-lease-internal.h +++ b/src/libsystemd-network/dhcp-server-lease-internal.h @@ -35,3 +35,6 @@ sd_dhcp_server_lease* dhcp_server_get_static_lease(sd_dhcp_server *server, const 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/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c index 1716044563..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) @@ -279,3 +283,187 @@ int dhcp_server_static_leases_append_json(sd_dhcp_server *server, JsonVariant ** 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 dd4cbd3064..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); } @@ -981,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; } @@ -1188,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; }} @@ -1343,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; @@ -1567,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/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); From 35b2c1b0c83b4cc8b240ba44359fccbfcc61b8ea Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 10 Mar 2024 14:59:53 +0900 Subject: [PATCH 12/14] fuzz-dhcp-server: also test saving and loading lease file --- src/libsystemd-network/fuzz-dhcp-server.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) 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; } From 5582b36c384fc522c23ef1ac032001882d033aff Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 1 Mar 2024 12:10:49 +0900 Subject: [PATCH 13/14] network/dhcp-server: save leases in state directory Then, we can read the lease file on restart, and the DHCP server will be able to manage previously assigned addresses. To save leases in the state directory /var/lib/systemd/network/, this adds systemd-networkd-dhcp-server.service, and by default systemd-networkd does not start the DHCP server without the heler service started. Closes #29991. --- man/systemd.network.xml | 8 +++- src/network/networkd-dhcp-server.c | 63 +++++++++++++++++++++++++- src/network/networkd-dhcp-server.h | 3 ++ src/network/networkd-manager-varlink.c | 3 ++ 4 files changed, 74 insertions(+), 3 deletions(-) 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/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-manager-varlink.c b/src/network/networkd-manager-varlink.c index a85751491e..f3b265fed5 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -5,6 +5,7 @@ #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" @@ -211,6 +212,8 @@ static int vl_method_set_persistent_storage(Varlink *vlink, JsonVariant *paramet 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); } From 1c4411b7ffc3becbe9c908a27f12951a2c47cd7d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 26 Feb 2024 15:22:11 +0900 Subject: [PATCH 14/14] test-network: add test case for DHCP server lease file --- test/test-network/systemd-networkd-tests.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 5200999018..1bf79d974c 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -5734,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()