]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-dhcp-server: introduce sd_dhcp_server_set_lease_file() and dhcp_server_{save,load...
authorYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 2 Jan 2024 21:07:17 +0000 (06:07 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 11 Mar 2024 16:57:17 +0000 (01:57 +0900)
The functionality is not used networkd yet in this commit, but will be
used in later commits.

src/libsystemd-network/dhcp-server-internal.h
src/libsystemd-network/dhcp-server-lease-internal.h
src/libsystemd-network/sd-dhcp-server-lease.c
src/libsystemd-network/sd-dhcp-server.c
src/systemd/sd-dhcp-server.h

index 6e4404fb735fec9f086b4e1844e55b3e2ecd6d88..8f5707c8e2895053e5b738a18aa161261d2a5d0f 100644 (file)
@@ -75,6 +75,8 @@ struct sd_dhcp_server {
 
         char *agent_circuit_id;
         char *agent_remote_id;
+
+        char *lease_file;
 };
 
 typedef struct DHCPRequest {
index 82ad1b0aedcac92dbf34b317370219b396d66368..518c1510941b072c1afe696c5d2915314fffa041 100644 (file)
@@ -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);
index 17160445632f90cb02b29e5eaf151e2916689963..f4c7e8d0a47d4d33df88a8005e9c62bc48500c78 100644 (file)
@@ -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;
+}
index dd4cbd3064b581eed123fa16c2756fb9457d6e02..bbe96be83463b87e9bcc71c77064e3b0087669b1 100644 (file)
@@ -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"
 #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);
+}
index feafa5d1fc69eab866d84fbefb62c29d3b295aed..486af48b25175a97512307d2afdffb03b5d1b99c 100644 (file)
@@ -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);