summaryrefslogtreecommitdiffstats
path: root/src/libsystemd-network/sd-dhcp-server-lease.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libsystemd-network/sd-dhcp-server-lease.c')
-rw-r--r--src/libsystemd-network/sd-dhcp-server-lease.c513
1 files changed, 513 insertions, 0 deletions
diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c
new file mode 100644
index 0000000..2f84d51
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp-server-lease.c
@@ -0,0 +1,513 @@
+/* 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 NULL;
+
+ if (lease->server) {
+ hashmap_remove_value(lease->server->bound_leases_by_address, UINT32_TO_PTR(lease->address), lease);
+ hashmap_remove_value(lease->server->bound_leases_by_client_id, &lease->client_id, lease);
+ hashmap_remove_value(lease->server->static_leases_by_address, UINT32_TO_PTR(lease->address), lease);
+ hashmap_remove_value(lease->server->static_leases_by_client_id, &lease->client_id, lease);
+ }
+
+ free(lease->hostname);
+ return mfree(lease);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_server_lease, sd_dhcp_server_lease, dhcp_server_lease_free);
+
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ dhcp_server_lease_hash_ops,
+ sd_dhcp_client_id,
+ client_id_hash_func,
+ client_id_compare_func,
+ sd_dhcp_server_lease,
+ sd_dhcp_server_lease_unref);
+
+int dhcp_server_put_lease(sd_dhcp_server *server, sd_dhcp_server_lease *lease, bool is_static) {
+ int r;
+
+ assert(server);
+ assert(lease);
+
+ lease->server = server; /* This must be set before hashmap_put(). */
+
+ r = hashmap_ensure_put(is_static ? &server->static_leases_by_client_id : &server->bound_leases_by_client_id,
+ &dhcp_server_lease_hash_ops, &lease->client_id, lease);
+ if (r < 0)
+ return r;
+
+ r = hashmap_ensure_put(is_static ? &server->static_leases_by_address : &server->bound_leases_by_address,
+ NULL, UINT32_TO_PTR(lease->address), lease);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *req, usec_t expiration) {
+ _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL;
+ int r;
+
+ assert(server);
+ assert(address != 0);
+ assert(req);
+ assert(expiration != 0);
+
+ /* If a lease for the host already exists, update it. */
+ lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id);
+ if (lease) {
+ if (lease->address != address) {
+ hashmap_remove_value(server->bound_leases_by_address, UINT32_TO_PTR(lease->address), lease);
+ lease->address = address;
+
+ r = hashmap_ensure_put(&server->bound_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease);
+ if (r < 0)
+ return r;
+ }
+
+ lease->expiration = expiration;
+
+ TAKE_PTR(lease);
+ return 0;
+ }
+
+ /* Otherwise, add a new lease. */
+
+ lease = new(sd_dhcp_server_lease, 1);
+ if (!lease)
+ return -ENOMEM;
+
+ *lease = (sd_dhcp_server_lease) {
+ .n_ref = 1,
+ .address = address,
+ .client_id = req->client_id,
+ .htype = req->message->htype,
+ .hlen = req->message->hlen,
+ .gateway = req->message->giaddr,
+ .expiration = expiration,
+ };
+
+ memcpy(lease->chaddr, req->message->chaddr, req->message->hlen);
+
+ if (req->hostname) {
+ lease->hostname = strdup(req->hostname);
+ if (!lease->hostname)
+ return -ENOMEM;
+ }
+
+ r = dhcp_server_put_lease(server, lease, /* is_static = */ false);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(lease);
+ return 0;
+}
+
+int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server) {
+ sd_dhcp_server_lease *lease;
+ usec_t time_now;
+ int r;
+
+ assert(server);
+
+ r = sd_event_now(server->event, CLOCK_BOOTTIME, &time_now);
+ if (r < 0)
+ return r;
+
+ HASHMAP_FOREACH(lease, server->bound_leases_by_client_id)
+ if (lease->expiration < time_now) {
+ log_dhcp_server(server, "CLEAN (0x%x)", be32toh(lease->address));
+ sd_dhcp_server_lease_unref(lease);
+ }
+
+ return 0;
+}
+
+sd_dhcp_server_lease* dhcp_server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req) {
+ sd_dhcp_server_lease *static_lease;
+ sd_dhcp_client_id client_id;
+
+ assert(server);
+ assert(req);
+
+ static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id);
+ if (static_lease)
+ goto verify;
+
+ /* when no lease is found based on the client id fall back to chaddr */
+ if (!client_id_data_size_is_valid(req->message->hlen))
+ return NULL;
+
+ if (sd_dhcp_client_id_set(&client_id, /* type = */ 1, req->message->chaddr, req->message->hlen) < 0)
+ return NULL;
+
+ static_lease = hashmap_get(server->static_leases_by_client_id, &client_id);
+ if (!static_lease)
+ return NULL;
+
+verify:
+ /* Check if the address is in the same subnet. */
+ if ((static_lease->address & server->netmask) != server->subnet)
+ return NULL;
+
+ /* Check if the address is different from the server address. */
+ if (static_lease->address == server->address)
+ return NULL;
+
+ return static_lease;
+}
+
+int sd_dhcp_server_set_static_lease(
+ sd_dhcp_server *server,
+ const struct in_addr *address,
+ uint8_t *client_id_raw,
+ size_t client_id_size) {
+
+ _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL;
+ sd_dhcp_client_id client_id;
+ int r;
+
+ assert_return(server, -EINVAL);
+ assert_return(client_id_raw, -EINVAL);
+ assert_return(client_id_size_is_valid(client_id_size), -EINVAL);
+ assert_return(!sd_dhcp_server_is_running(server), -EBUSY);
+
+ r = sd_dhcp_client_id_set_raw(&client_id, client_id_raw, client_id_size);
+ if (r < 0)
+ return r;
+
+ /* Static lease with an empty or omitted address is a valid entry,
+ * the server removes any static lease with the specified mac address. */
+ if (!address || address->s_addr == 0) {
+ sd_dhcp_server_lease_unref(hashmap_get(server->static_leases_by_client_id, &client_id));
+ return 0;
+ }
+
+ lease = new(sd_dhcp_server_lease, 1);
+ if (!lease)
+ return -ENOMEM;
+
+ *lease = (sd_dhcp_server_lease) {
+ .n_ref = 1,
+ .address = address->s_addr,
+ .client_id = client_id,
+ };
+
+ r = dhcp_server_put_lease(server, lease, /* is_static = */ true);
+ if (r < 0)
+ return r;
+
+ 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_free_ 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),
+ JSON_BUILD_PAIR_IN4_ADDR("Address", &(struct in_addr) { .s_addr = server->address }),
+ JSON_BUILD_PAIR_UNSIGNED("PrefixLength",
+ in4_addr_netmask_to_prefixlen(&(struct in_addr) { .s_addr = server->netmask }))));
+ if (r < 0)
+ return r;
+
+ r = dhcp_server_bound_leases_append_json(server, &v);
+ if (r < 0)
+ return r;
+
+ r = mkdirat_parents(server->lease_dir_fd, server->lease_file, 0755);
+ if (r < 0)
+ return r;
+
+ r = fopen_temporary_at(server->lease_dir_fd, 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)
+ goto failure;
+
+ r = conservative_renameat(server->lease_dir_fd, temp_path, server->lease_dir_fd, server->lease_file);
+ if (r < 0)
+ goto failure;
+
+ return 0;
+
+failure:
+ (void) unlinkat(server->lease_dir_fd, temp_path, /* flags = */ 0);
+ return r;
+}
+
+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;
+ struct in_addr address;
+ uint8_t prefixlen;
+ JsonVariant *leases;
+} SavedInfo;
+
+static void saved_info_done(SavedInfo *info) {
+ if (!info)
+ return;
+
+ json_variant_unref(info->leases);
+}
+
+static int load_leases_file(int dir_fd, const char *path, SavedInfo *ret) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ int r;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(ret);
+
+ r = json_parse_file_at(
+ /* f = */ NULL,
+ dir_fd,
+ path,
+ /* flags = */ 0,
+ &v,
+ /* ret_line = */ NULL,
+ /* ret_column = */ NULL);
+ if (r < 0)
+ return r;
+
+ static const JsonDispatch dispatch_lease_file_table[] = {
+ { "BootID", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(SavedInfo, boot_id), JSON_MANDATORY },
+ { "Address", JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(SavedInfo, address), JSON_MANDATORY },
+ { "PrefixLength", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint8, offsetof(SavedInfo, prefixlen), JSON_MANDATORY },
+ { "Leases", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(SavedInfo, leases), JSON_MANDATORY },
+ {}
+ };
+
+ return json_dispatch(v, dispatch_lease_file_table, JSON_ALLOW_EXTENSIONS, ret);
+}
+
+int dhcp_server_load_leases(sd_dhcp_server *server) {
+ _cleanup_(saved_info_done) SavedInfo info = {};
+ sd_id128_t boot_id;
+ size_t n, m;
+ int r;
+
+ assert(server);
+ assert(server->event);
+
+ if (!server->lease_file)
+ return 0;
+
+ r = load_leases_file(server->lease_dir_fd, server->lease_file, &info);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return r;
+
+ r = sd_id128_get_boot(&boot_id);
+ if (r < 0)
+ return r;
+
+ n = hashmap_size(server->bound_leases_by_client_id);
+
+ 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)));
+
+ 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;
+}
+
+int dhcp_server_leases_file_get_server_address(
+ int dir_fd,
+ const char *path,
+ struct in_addr *ret_address,
+ uint8_t *ret_prefixlen) {
+
+ _cleanup_(saved_info_done) SavedInfo info = {};
+ int r;
+
+ if (!ret_address && !ret_prefixlen)
+ return 0;
+
+ r = load_leases_file(dir_fd, path, &info);
+ if (r < 0)
+ return r;
+
+ if (ret_address)
+ *ret_address = info.address;
+ if (ret_prefixlen)
+ *ret_prefixlen = info.prefixlen;
+ return 0;
+}