/* 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)
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;
+}
#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
free(server->agent_circuit_id);
free(server->agent_remote_id);
+ free(server->lease_file);
+
free(server->ifname);
return mfree(server);
}
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;
}
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;
}}
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;
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);
+}