From 2dead8129f7b6fe644e17e1dc1739bebacfe1364 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Sun, 25 May 2014 18:28:03 +0200 Subject: [PATCH] sd-dhcp-server: add basic REQUEST/ACK support --- src/libsystemd-network/dhcp-server-internal.h | 3 + src/libsystemd-network/sd-dhcp-server.c | 136 +++++++++++++++++- src/libsystemd-network/test-dhcp-server.c | 37 +++++ src/systemd/sd-dhcp-server.h | 1 + 4 files changed, 172 insertions(+), 5 deletions(-) diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index 381304ee4..cd480e7bb 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -40,6 +40,8 @@ struct sd_dhcp_server { int index; be32_t address; + be32_t pool_start; + size_t pool_size; }; typedef struct DHCPClientId { @@ -55,6 +57,7 @@ typedef struct DHCPRequest { DHCPClientId client_id; size_t max_optlen; be32_t server_id; + be32_t requested_ip; } DHCPRequest; DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_server*, sd_dhcp_server_unref); diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 07715c5e9..e170cfabf 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -29,6 +29,21 @@ #define DHCP_DEFAULT_LEASE_TIME 60 +int sd_dhcp_server_set_lease_pool(sd_dhcp_server *server, struct in_addr *address, + size_t size) { + assert_return(server, -EINVAL); + assert_return(address, -EINVAL); + assert_return(address->s_addr, -EINVAL); + assert_return(size, -EINVAL); + assert_return(server->pool_start == htobe32(INADDR_ANY), -EBUSY); + assert_return(!server->pool_size, -EBUSY); + + server->pool_start = address->s_addr; + server->pool_size = size; + + return 0; +} + int sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address) { assert_return(server, -EINVAL); assert_return(address, -EINVAL); @@ -288,7 +303,7 @@ static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret, assert(server); assert(ret); assert(_optoffset); - assert(type == DHCP_OFFER); + assert(IN_SET(type, DHCP_OFFER, DHCP_ACK)); packet = malloc0(sizeof(DHCPPacket) + req->max_optlen); if (!packet) @@ -310,7 +325,7 @@ static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret, return 0; } -static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req) { +static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req, be32_t address) { _cleanup_free_ DHCPPacket *packet = NULL; size_t offset; be32_t lease_time; @@ -320,8 +335,7 @@ static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req) { if (r < 0) return r; - /* for now offer a random IP */ - packet->dhcp.yiaddr = random_u32(); + packet->dhcp.yiaddr = address; /* for one minute */ lease_time = htobe32(DHCP_DEFAULT_LEASE_TIME); @@ -337,6 +351,32 @@ static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req) { return 0; } +static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, be32_t address) { + _cleanup_free_ DHCPPacket *packet = NULL; + size_t offset; + be32_t lease_time; + int r; + + r = server_message_init(server, &packet, DHCP_ACK, &offset, req); + if (r < 0) + return r; + + packet->dhcp.yiaddr = address; + + /* for ten seconds */ + lease_time = htobe32(DHCP_DEFAULT_LEASE_TIME); + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time); + if (r < 0) + return r; + + r = dhcp_server_send_packet(server, req, packet, DHCP_ACK, offset); + if (r < 0) + return r; + + return 0; +} + static int parse_request(uint8_t code, uint8_t len, const uint8_t *option, void *user_data) { DHCPRequest *req = user_data; @@ -344,6 +384,11 @@ static int parse_request(uint8_t code, uint8_t len, const uint8_t *option, assert(req); switch(code) { + case DHCP_OPTION_REQUESTED_IP_ADDRESS: + if (len == 4) + req->requested_ip = *(be32_t*)option; + + break; case DHCP_OPTION_SERVER_IDENTIFIER: if (len == 4) req->server_id = *(be32_t*)option; @@ -439,10 +484,21 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, switch(type) { case DHCP_DISCOVER: + { + be32_t address; + log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->xid)); - r = server_send_offer(server, req); + if (!server->pool_size) + /* 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)); + + r = server_send_offer(server, req, address); if (r < 0) { /* this only fails on critical errors */ log_dhcp_server(server, "could not send offer: %s", @@ -456,6 +512,76 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, break; } + case DHCP_REQUEST: + { + be32_t address; + + /* see RFC 2131, section 4.3.2 */ + + if (req->server_id) { + log_dhcp_server(server, "REQUEST (selecting) (0x%x)", + be32toh(req->message->xid)); + + /* SELECTING */ + if (req->server_id != server->address) + /* client did not pick us */ + return 0; + + if (req->message->ciaddr) + /* this MUST be zero */ + return 0; + + if (!req->requested_ip) + /* this must be filled in with the yiaddr + from the chosen OFFER */ + return 0; + + address = req->requested_ip; + } else if (req->requested_ip) { + log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)", + be32toh(req->message->xid)); + + /* INIT-REBOOT */ + if (req->message->ciaddr) + /* this MUST be zero */ + return 0; + + /* TODO: check if requested IP is correct, NAK if not */ + address = req->requested_ip; + } else { + log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)", + be32toh(req->message->xid)); + + /* REBINDING / RENEWING */ + if (!req->message->ciaddr) + /* this MUST be filled in with clients IP address */ + return 0; + + 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) { + 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)); + return r; + } else { + log_dhcp_server(server, "ACK (0x%x)", + be32toh(req->message->xid)); + return DHCP_ACK; + } + } else + return 0; + + break; + } + } return 0; } diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index a252e7022..10fc2368c 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -59,6 +59,11 @@ static void test_basic(sd_event *event) { assert_se(sd_dhcp_server_set_address(server, &address_lo) >= 0); assert_se(sd_dhcp_server_set_address(server, &address_lo) == -EBUSY); + assert_se(sd_dhcp_server_set_lease_pool(server, &address_any, 1) == -EINVAL); + assert_se(sd_dhcp_server_set_lease_pool(server, &address_lo, 0) == -EINVAL); + assert_se(sd_dhcp_server_set_lease_pool(server, &address_lo, 1) >= 0); + assert_se(sd_dhcp_server_set_lease_pool(server, &address_lo, 1) == -EBUSY); + assert_se(sd_dhcp_server_start(server) >= 0); assert_se(sd_dhcp_server_start(server) == -EBUSY); assert_se(sd_dhcp_server_stop(server) >= 0); @@ -75,11 +80,23 @@ static void test_message_handler(void) { uint8_t length; uint8_t type; } _packed_ option_type; + struct { + uint8_t code; + uint8_t length; + be32_t address; + } _packed_ option_requested_ip; + struct { + uint8_t code; + uint8_t length; + be32_t address; + } _packed_ option_server_id; uint8_t end; } _packed_ test = { .message.op = BOOTREQUEST, .message.htype = ARPHRD_ETHER, .message.hlen = ETHER_ADDR_LEN, + .message.xid = htobe32(0x12345678), + .message.chaddr = { 'A', 'B', 'C', 'D', 'E', 'F' }, .option_type.code = DHCP_OPTION_MESSAGE_TYPE, .option_type.length = 1, .option_type.type = DHCP_DISCOVER, @@ -94,6 +111,8 @@ static void test_message_handler(void) { assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0); assert_se(sd_dhcp_server_start(server) >= 0); + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); + assert_se(sd_dhcp_server_set_lease_pool(server, &address_lo, 10) >= 0); assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER); test.end = 0; @@ -125,6 +144,24 @@ static void test_message_handler(void) { assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); test.message.hlen = ETHER_ADDR_LEN; assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER); + + test.option_type.type = DHCP_REQUEST; + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); + test.option_requested_ip.code = DHCP_OPTION_REQUESTED_IP_ADDRESS; + test.option_requested_ip.length = 4; + test.option_requested_ip.address = htobe32(0x12345678); + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); + test.option_server_id.code = DHCP_OPTION_SERVER_IDENTIFIER; + test.option_server_id.length = 4; + 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 + 3); + test.option_requested_ip.address = htobe32(0x12345678); + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); } int main(int argc, char *argv[]) { diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h index 5edeffc31..cd0ff72af 100644 --- a/src/systemd/sd-dhcp-server.h +++ b/src/systemd/sd-dhcp-server.h @@ -42,4 +42,5 @@ int sd_dhcp_server_start(sd_dhcp_server *server); int sd_dhcp_server_stop(sd_dhcp_server *server); int sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address); +int sd_dhcp_server_set_lease_pool(sd_dhcp_server *server, struct in_addr *start, size_t size); #endif -- 2.30.2