mirror of
https://github.com/systemd/systemd.git
synced 2024-12-23 21:35:11 +03:00
sd-dhcp-server: track bound leases
Make sure we don't hand out the same IP twice. We still don't handle lease expiry.
This commit is contained in:
parent
c7d9ffe6d6
commit
87322b3aee
@ -23,12 +23,25 @@
|
||||
#include "sd-event.h"
|
||||
#include "sd-dhcp-server.h"
|
||||
|
||||
#include "hashmap.h"
|
||||
#include "refcnt.h"
|
||||
#include "util.h"
|
||||
#include "log.h"
|
||||
|
||||
#include "dhcp-internal.h"
|
||||
|
||||
typedef struct DHCPClientId {
|
||||
size_t length;
|
||||
uint8_t *data;
|
||||
} DHCPClientId;
|
||||
|
||||
typedef struct DHCPLease {
|
||||
DHCPClientId client_id;
|
||||
|
||||
be32_t address;
|
||||
usec_t expiration;
|
||||
} DHCPLease;
|
||||
|
||||
struct sd_dhcp_server {
|
||||
RefCount n_ref;
|
||||
|
||||
@ -42,12 +55,11 @@ struct sd_dhcp_server {
|
||||
be32_t address;
|
||||
be32_t pool_start;
|
||||
size_t pool_size;
|
||||
};
|
||||
size_t next_offer;
|
||||
|
||||
typedef struct DHCPClientId {
|
||||
size_t length;
|
||||
uint8_t *data;
|
||||
} DHCPClientId;
|
||||
Hashmap *leases_by_client_id;
|
||||
DHCPLease **bound_leases;
|
||||
};
|
||||
|
||||
typedef struct DHCPRequest {
|
||||
/* received message */
|
||||
@ -71,3 +83,6 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
|
||||
int dhcp_server_send_packet(sd_dhcp_server *server,
|
||||
DHCPRequest *req, DHCPPacket *packet,
|
||||
int type, size_t optoffset);
|
||||
|
||||
unsigned long client_id_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]);
|
||||
int client_id_compare_func(const void *_a, const void *_b);
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include <sys/ioctl.h>
|
||||
#include <netinet/if_ether.h>
|
||||
|
||||
#include "siphash24.h"
|
||||
|
||||
#include "sd-dhcp-server.h"
|
||||
#include "dhcp-server-internal.h"
|
||||
#include "dhcp-internal.h"
|
||||
@ -37,6 +39,11 @@ int sd_dhcp_server_set_lease_pool(sd_dhcp_server *server, struct in_addr *addres
|
||||
assert_return(size, -EINVAL);
|
||||
assert_return(server->pool_start == htobe32(INADDR_ANY), -EBUSY);
|
||||
assert_return(!server->pool_size, -EBUSY);
|
||||
assert_return(!server->bound_leases, -EBUSY);
|
||||
|
||||
server->bound_leases = new0(DHCPLease*, size);
|
||||
if (!server->bound_leases)
|
||||
return -ENOMEM;
|
||||
|
||||
server->pool_start = address->s_addr;
|
||||
server->pool_size = size;
|
||||
@ -62,13 +69,63 @@ sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
|
||||
return server;
|
||||
}
|
||||
|
||||
unsigned long client_id_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) {
|
||||
uint64_t u;
|
||||
const DHCPClientId *id = p;
|
||||
|
||||
assert(id);
|
||||
assert(id->length);
|
||||
assert(id->data);
|
||||
|
||||
siphash24((uint8_t*) &u, id->data, id->length, hash_key);
|
||||
|
||||
return (unsigned long) u;
|
||||
}
|
||||
|
||||
int client_id_compare_func(const void *_a, const void *_b) {
|
||||
const DHCPClientId *a, *b;
|
||||
|
||||
a = _a;
|
||||
b = _b;
|
||||
|
||||
assert(!a->length || a->data);
|
||||
assert(!b->length || b->data);
|
||||
|
||||
if (a->length != b->length)
|
||||
return a->length < b->length ? -1 : 1;
|
||||
|
||||
return memcmp(a->data, b->data, a->length);
|
||||
}
|
||||
|
||||
static void dhcp_lease_free(DHCPLease *lease) {
|
||||
if (!lease)
|
||||
return;
|
||||
|
||||
free(lease->client_id.data);
|
||||
free(lease);
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPLease*, dhcp_lease_free);
|
||||
#define _cleanup_dhcp_lease_free_ _cleanup_(dhcp_lease_freep)
|
||||
|
||||
sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) {
|
||||
if (server && REFCNT_DEC(server->n_ref) <= 0) {
|
||||
DHCPLease *lease;
|
||||
Iterator i;
|
||||
|
||||
log_dhcp_server(server, "UNREF");
|
||||
|
||||
sd_dhcp_server_stop(server);
|
||||
|
||||
sd_event_unref(server->event);
|
||||
|
||||
HASHMAP_FOREACH(lease, server->leases_by_client_id, i) {
|
||||
hashmap_remove(server->leases_by_client_id, lease);
|
||||
dhcp_lease_free(lease);
|
||||
}
|
||||
|
||||
hashmap_free(server->leases_by_client_id);
|
||||
free(server->bound_leases);
|
||||
free(server);
|
||||
}
|
||||
|
||||
@ -90,6 +147,7 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
|
||||
server->fd = -1;
|
||||
server->address = htobe32(INADDR_ANY);
|
||||
server->index = ifindex;
|
||||
server->leases_by_client_id = hashmap_new(client_id_hash_func, client_id_compare_func);
|
||||
|
||||
*ret = server;
|
||||
server = NULL;
|
||||
@ -478,9 +536,24 @@ static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_pool_offset(sd_dhcp_server *server, be32_t requested_ip) {
|
||||
assert(server);
|
||||
|
||||
if (!server->pool_size)
|
||||
return -EINVAL;
|
||||
|
||||
if (be32toh(requested_ip) < be32toh(server->pool_start) ||
|
||||
be32toh(requested_ip) >= be32toh(server->pool_start) +
|
||||
+ server->pool_size)
|
||||
return -EINVAL;
|
||||
|
||||
return be32toh(requested_ip) - be32toh(server->pool_start);
|
||||
}
|
||||
|
||||
int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
|
||||
size_t length) {
|
||||
_cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
|
||||
DHCPLease *existing_lease;
|
||||
int type, r;
|
||||
|
||||
assert(server);
|
||||
@ -504,10 +577,13 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
|
||||
/* this only fails on critical errors */
|
||||
return r;
|
||||
|
||||
existing_lease = hashmap_get(server->leases_by_client_id, &req->client_id);
|
||||
|
||||
switch(type) {
|
||||
case DHCP_DISCOVER:
|
||||
{
|
||||
be32_t address;
|
||||
be32_t address = INADDR_ANY;
|
||||
unsigned i;
|
||||
|
||||
log_dhcp_server(server, "DISCOVER (0x%x)",
|
||||
be32toh(req->message->xid));
|
||||
@ -516,9 +592,22 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
|
||||
/* no pool allocated */
|
||||
return 0;
|
||||
|
||||
/* for now pick a random address from the pool */
|
||||
address = htobe32(be32toh(server->pool_start) +
|
||||
(random_u32() % server->pool_size));
|
||||
/* for now pick a random free address from the pool */
|
||||
if (existing_lease)
|
||||
address = existing_lease->address;
|
||||
else {
|
||||
for (i = 0; i < server->pool_size; i++) {
|
||||
if (!server->bound_leases[server->next_offer]) {
|
||||
address = htobe32(be32toh(server->pool_start) + server->next_offer);
|
||||
break;
|
||||
} else
|
||||
server->next_offer = (server->next_offer + 1) % server->pool_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (address == INADDR_ANY)
|
||||
/* no free addresses left */
|
||||
return 0;
|
||||
|
||||
r = server_send_offer(server, req, address);
|
||||
if (r < 0) {
|
||||
@ -538,6 +627,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
|
||||
{
|
||||
be32_t address;
|
||||
bool init_reboot = false;
|
||||
int pool_offset;
|
||||
|
||||
/* see RFC 2131, section 4.3.2 */
|
||||
|
||||
@ -584,20 +674,48 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
|
||||
address = req->message->ciaddr;
|
||||
}
|
||||
|
||||
/* for now we just verify that the address is from the pool, not
|
||||
whether or not it is taken */
|
||||
if (htobe32(req->requested_ip) >= htobe32(server->pool_start) &&
|
||||
htobe32(req->requested_ip) < htobe32(server->pool_start) +
|
||||
+ server->pool_size) {
|
||||
pool_offset = get_pool_offset(server, address);
|
||||
|
||||
/* verify that the requested address is from the pool, and either
|
||||
owned by the current client or free */
|
||||
if (pool_offset >= 0 &&
|
||||
server->bound_leases[pool_offset] == existing_lease) {
|
||||
DHCPLease *lease;
|
||||
usec_t time_now;
|
||||
|
||||
if (!existing_lease) {
|
||||
lease = new0(DHCPLease, 1);
|
||||
lease->address = req->requested_ip;
|
||||
lease->client_id.data = memdup(req->client_id.data,
|
||||
req->client_id.length);
|
||||
if (!lease->client_id.data)
|
||||
return -ENOMEM;
|
||||
lease->client_id.length = req->client_id.length;
|
||||
} else
|
||||
lease = existing_lease;
|
||||
|
||||
r = sd_event_now(server->event, CLOCK_MONOTONIC, &time_now);
|
||||
if (r < 0)
|
||||
time_now = now(CLOCK_MONOTONIC);
|
||||
lease->expiration = req->lifetime * USEC_PER_SEC + time_now;
|
||||
|
||||
r = server_send_ack(server, req, address);
|
||||
if (r < 0) {
|
||||
/* this only fails on critical errors */
|
||||
log_dhcp_server(server, "could not send ack: %s",
|
||||
strerror(-r));
|
||||
|
||||
if (!existing_lease)
|
||||
dhcp_lease_free(lease);
|
||||
|
||||
return r;
|
||||
} else {
|
||||
log_dhcp_server(server, "ACK (0x%x)",
|
||||
be32toh(req->message->xid));
|
||||
|
||||
server->bound_leases[pool_offset] = lease;
|
||||
hashmap_put(server->leases_by_client_id, &lease->client_id, lease);
|
||||
|
||||
return DHCP_ACK;
|
||||
}
|
||||
} else if (init_reboot) {
|
||||
|
@ -90,6 +90,11 @@ static void test_message_handler(void) {
|
||||
uint8_t length;
|
||||
be32_t address;
|
||||
} _packed_ option_server_id;
|
||||
struct {
|
||||
uint8_t code;
|
||||
uint8_t length;
|
||||
uint8_t id[7];
|
||||
} _packed_ option_client_id;
|
||||
uint8_t end;
|
||||
} _packed_ test = {
|
||||
.message.op = BOOTREQUEST,
|
||||
@ -156,14 +161,67 @@ static void test_message_handler(void) {
|
||||
test.option_server_id.address = htobe32(INADDR_LOOPBACK);
|
||||
test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
|
||||
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK);
|
||||
|
||||
test.option_server_id.address = htobe32(0x12345678);
|
||||
test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
|
||||
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
|
||||
test.option_server_id.address = htobe32(INADDR_LOOPBACK);
|
||||
test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 4);
|
||||
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
|
||||
test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
|
||||
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK);
|
||||
|
||||
test.option_client_id.code = DHCP_OPTION_CLIENT_IDENTIFIER;
|
||||
test.option_client_id.length = 7;
|
||||
test.option_client_id.id[0] = 0x01;
|
||||
test.option_client_id.id[1] = 'A';
|
||||
test.option_client_id.id[2] = 'B';
|
||||
test.option_client_id.id[3] = 'C';
|
||||
test.option_client_id.id[4] = 'D';
|
||||
test.option_client_id.id[5] = 'E';
|
||||
test.option_client_id.id[6] = 'F';
|
||||
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK);
|
||||
|
||||
test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 30);
|
||||
assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
|
||||
}
|
||||
|
||||
static void test_client_id_hash(void) {
|
||||
DHCPClientId a = {
|
||||
.length = 4,
|
||||
}, b = {
|
||||
.length = 4,
|
||||
};
|
||||
uint8_t hash_key[HASH_KEY_SIZE] = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
|
||||
};
|
||||
|
||||
a.data = (uint8_t*)strdup("abcd");
|
||||
b.data = (uint8_t*)strdup("abcd");
|
||||
|
||||
assert_se(client_id_compare_func(&a, &b) == 0);
|
||||
assert_se(client_id_hash_func(&a, hash_key) == client_id_hash_func(&b, hash_key));
|
||||
a.length = 3;
|
||||
assert_se(client_id_compare_func(&a, &b) != 0);
|
||||
a.length = 4;
|
||||
assert_se(client_id_compare_func(&a, &b) == 0);
|
||||
assert_se(client_id_hash_func(&a, hash_key) == client_id_hash_func(&b, hash_key));
|
||||
|
||||
b.length = 3;
|
||||
assert_se(client_id_compare_func(&a, &b) != 0);
|
||||
b.length = 4;
|
||||
assert_se(client_id_compare_func(&a, &b) == 0);
|
||||
assert_se(client_id_hash_func(&a, hash_key) == client_id_hash_func(&b, hash_key));
|
||||
|
||||
free(b.data);
|
||||
b.data = (uint8_t*)strdup("abce");
|
||||
assert_se(client_id_compare_func(&a, &b) != 0);
|
||||
|
||||
free(a.data);
|
||||
free(b.data);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
_cleanup_event_unref_ sd_event *e;
|
||||
|
||||
@ -175,6 +233,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
test_basic(e);
|
||||
test_message_handler();
|
||||
test_client_id_hash();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user