X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Flibsystemd-network%2Fsd-dhcp-server.c;h=2d3eaa35e396c245fe5b1e0369f73decc44dc178;hp=e4396a0c96cde435b6439910db3fd512bc11cf20;hb=c15fb62a731f1a457af94e60ac6a4d23f219a8f6;hpb=3a864fe4a894745ac61f1ecabd7cadf04139a284 diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index e4396a0c9..2d3eaa35e 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -21,11 +21,53 @@ ***/ #include +#include + +#include "siphash24.h" #include "sd-dhcp-server.h" #include "dhcp-server-internal.h" #include "dhcp-internal.h" +#define DHCP_DEFAULT_LEASE_TIME 3600 /* one hour */ + +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); + 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; + + return 0; +} + +int sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address) { + assert_return(server, -EINVAL); + assert_return(address, -EINVAL); + assert_return(address->s_addr, -EINVAL); + assert_return(server->address == htobe32(INADDR_ANY), -EBUSY); + + server->address = address->s_addr; + + return 0; +} + +bool sd_dhcp_server_is_running(sd_dhcp_server *server) { + assert_return(server, -EINVAL); + + return !!server->receive_message; +} + sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) { if (server) assert_se(REFCNT_INC(server->n_ref) >= 2); @@ -33,15 +75,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); +} + sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) { - if (server && REFCNT_DEC(server->n_ref) <= 0) { - log_dhcp_server(server, "UNREF"); + DHCPLease *lease; - sd_dhcp_server_stop(server); + if (!server) + return NULL; - sd_event_unref(server->event); - free(server); - } + if (REFCNT_DEC(server->n_ref) > 0) + return NULL; + + log_dhcp_server(server, "UNREF"); + + sd_dhcp_server_stop(server); + + sd_event_unref(server->event); + + while ((lease = hashmap_steal_first(server->leases_by_client_id))) + dhcp_lease_free(lease); + hashmap_free(server->leases_by_client_id); + + free(server->bound_leases); + free(server); return NULL; } @@ -57,8 +147,11 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) { return -ENOMEM; server->n_ref = REFCNT_INIT; + server->fd_raw = -1; 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; @@ -105,6 +198,7 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) { server->receive_message = sd_event_source_unref(server->receive_message); + server->fd_raw = safe_close(server->fd_raw); server->fd = safe_close(server->fd); log_dhcp_server(server, "STOPPED"); @@ -112,9 +206,622 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) { return 0; } +static int dhcp_server_send_unicast_raw(sd_dhcp_server *server, DHCPPacket *packet, + size_t len) { + union sockaddr_union link = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htons(ETH_P_IP), + .ll.sll_ifindex = server->index, + .ll.sll_halen = ETH_ALEN, + }; + int r; + + assert(server); + assert(server->index > 0); + assert(server->address); + assert(packet); + assert(len > sizeof(DHCPPacket)); + + memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN); + + dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER, + packet->dhcp.yiaddr, DHCP_PORT_CLIENT, len); + + r = dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len); + if (r < 0) + return r; + + return 0; +} + +static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination, + DHCPMessage *message, size_t len) { + union sockaddr_union dest = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(DHCP_PORT_CLIENT), + .in.sin_addr.s_addr = destination, + }; + struct iovec iov = { + .iov_base = message, + .iov_len = len, + }; + uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))] = {}; + struct msghdr msg = { + .msg_name = &dest, + .msg_namelen = sizeof(dest.in), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = sizeof(cmsgbuf), + }; + struct cmsghdr *cmsg; + struct in_pktinfo *pktinfo; + int r; + + assert(server); + assert(server->fd > 0); + assert(message); + assert(len > sizeof(DHCPMessage)); + + cmsg = CMSG_FIRSTHDR(&msg); + assert(cmsg); + + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + + /* we attach source interface and address info to the message + rather than binding the socket. This will be mostly useful + when we gain support for arbitrary number of server addresses + */ + pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg); + assert(pktinfo); + + pktinfo->ipi_ifindex = server->index; + pktinfo->ipi_spec_dst.s_addr = server->address; + + r = sendmsg(server->fd, &msg, 0); + if (r < 0) + return -errno; + + return 0; +} + +static bool requested_broadcast(DHCPRequest *req) { + assert(req); + + return req->message->flags & htobe16(0x8000); +} + +int dhcp_server_send_packet(sd_dhcp_server *server, + DHCPRequest *req, DHCPPacket *packet, + int type, size_t optoffset) { + be32_t destination = INADDR_ANY; + int r; + + assert(server); + assert(req); + assert(req->max_optlen); + assert(optoffset <= req->max_optlen); + assert(packet); + + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, + DHCP_OPTION_SERVER_IDENTIFIER, + 4, &server->address); + if (r < 0) + return r; + + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, + DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + /* RFC 2131 Section 4.1 + + If the ’giaddr’ field in a DHCP message from a client is non-zero, + the server sends any return messages to the ’DHCP server’ port on the + BOOTP relay agent whose address appears in ’giaddr’. If the ’giaddr’ + field is zero and the ’ciaddr’ field is nonzero, then the server + unicasts DHCPOFFER and DHCPACK messages to the address in ’ciaddr’. + If ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is + set, then the server broadcasts DHCPOFFER and DHCPACK messages to + 0xffffffff. If the broadcast bit is not set and ’giaddr’ is zero and + ’ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK + messages to the client’s hardware address and ’yiaddr’ address. In + all cases, when ’giaddr’ is zero, the server broadcasts any DHCPNAK + messages to 0xffffffff. + + Section 4.3.2 + + If ’giaddr’ is set in the DHCPREQUEST message, the client is on a + different subnet. The server MUST set the broadcast bit in the + DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the + client, because the client may not have a correct network address + or subnet mask, and the client may not be answering ARP requests. + */ + if (req->message->giaddr) { + destination = req->message->giaddr; + if (type == DHCP_NAK) + packet->dhcp.flags = htobe16(0x8000); + } else if (req->message->ciaddr && type != DHCP_NAK) + destination = req->message->ciaddr; + + if (destination != INADDR_ANY) + return dhcp_server_send_udp(server, destination, &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + else if (requested_broadcast(req) || type == DHCP_NAK) + return dhcp_server_send_udp(server, INADDR_BROADCAST, &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + else + /* we cannot send UDP packet to specific MAC address when the address is + not yet configured, so must fall back to raw packets */ + return dhcp_server_send_unicast_raw(server, packet, + sizeof(DHCPPacket) + optoffset); +} + +static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret, + uint8_t type, size_t *_optoffset, DHCPRequest *req) { + _cleanup_free_ DHCPPacket *packet = NULL; + size_t optoffset = 0; + int r; + + assert(server); + assert(ret); + assert(_optoffset); + assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK)); + + packet = malloc0(sizeof(DHCPPacket) + req->max_optlen); + if (!packet) + return -ENOMEM; + + r = dhcp_message_init(&packet->dhcp, BOOTREPLY, be32toh(req->message->xid), + type, req->max_optlen, &optoffset); + if (r < 0) + return r; + + packet->dhcp.flags = req->message->flags; + packet->dhcp.giaddr = req->message->giaddr; + memcpy(&packet->dhcp.chaddr, &req->message->chaddr, ETH_ALEN); + + *_optoffset = optoffset; + *ret = packet; + packet = NULL; + + return 0; +} + +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; + int r; + + r = server_message_init(server, &packet, DHCP_OFFER, &offset, req); + if (r < 0) + return r; + + packet->dhcp.yiaddr = address; + + lease_time = htobe32(req->lifetime); + 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_OFFER, offset); + if (r < 0) + return r; + + 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; + + lease_time = htobe32(req->lifetime); + 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 server_send_nak(sd_dhcp_server *server, DHCPRequest *req) { + _cleanup_free_ DHCPPacket *packet = NULL; + size_t offset; + int r; + + r = server_message_init(server, &packet, DHCP_NAK, &offset, req); + if (r < 0) + return r; + + r = dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset); + if (r < 0) + return r; + + return 0; +} + +static int server_send_forcerenew(sd_dhcp_server *server, be32_t address, be32_t gateway, + uint8_t chaddr[]) { + _cleanup_free_ DHCPPacket *packet = NULL; + size_t optoffset = 0; + int r; + + assert(server); + assert(address != INADDR_ANY); + assert(chaddr); + + packet = malloc0(sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE); + if (!packet) + return -ENOMEM; + + r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0, + DHCP_FORCERENEW, DHCP_MIN_OPTIONS_SIZE, + &optoffset); + if (r < 0) + return r; + + r = dhcp_option_append(&packet->dhcp, DHCP_MIN_OPTIONS_SIZE, + &optoffset, 0, DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + memcpy(&packet->dhcp.chaddr, chaddr, ETH_ALEN); + + r = dhcp_server_send_udp(server, address, &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + 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; + + assert(req); + + switch(code) { + case DHCP_OPTION_IP_ADDRESS_LEASE_TIME: + if (len == 4) + req->lifetime = be32toh(*(be32_t*)option); + + break; + 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; + + break; + case DHCP_OPTION_CLIENT_IDENTIFIER: + if (len >= 2) { + uint8_t *data; + + data = memdup(option, len); + if (!data) + return -ENOMEM; + + free(req->client_id.data); + req->client_id.data = data; + req->client_id.length = len; + } + + break; + case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE: + if (len == 2) + req->max_optlen = be16toh(*(be16_t*)option) - + - sizeof(DHCPPacket); + + break; + } + + return 0; +} + +static void dhcp_request_free(DHCPRequest *req) { + if (!req) + return; + + free(req->client_id.data); + free(req); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free); +#define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep) + +static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) { + assert(req); + assert(message); + + req->message = message; + + /* set client id based on mac address if client did not send an explicit one */ + if (!req->client_id.data) { + uint8_t *data; + + data = new0(uint8_t, ETH_ALEN + 1); + if (!data) + return -ENOMEM; + + req->client_id.length = ETH_ALEN + 1; + req->client_id.data = data; + req->client_id.data[0] = 0x01; + memcpy(&req->client_id.data[1], &message->chaddr, ETH_ALEN); + } + + if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE) + req->max_optlen = DHCP_MIN_OPTIONS_SIZE; + + if (!req->lifetime) + req->lifetime = DHCP_DEFAULT_LEASE_TIME; + + 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); + assert(message); + + if (message->op != BOOTREQUEST || + message->htype != ARPHRD_ETHER || + message->hlen != ETHER_ADDR_LEN) + return 0; + + req = new0(DHCPRequest, 1); + if (!req) + return -ENOMEM; + + type = dhcp_option_parse(message, length, parse_request, req); + if (type < 0) + return 0; + + r = ensure_sane_request(req, message); + if (r < 0) + /* 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 = INADDR_ANY; + unsigned i; + + log_dhcp_server(server, "DISCOVER (0x%x)", + be32toh(req->message->xid)); + + if (!server->pool_size) + /* no pool allocated */ + return 0; + + /* 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) { + /* this only fails on critical errors */ + log_dhcp_server(server, "could not send offer: %s", + strerror(-r)); + return r; + } else { + log_dhcp_server(server, "OFFER (0x%x)", + be32toh(req->message->xid)); + return DHCP_OFFER; + } + + break; + } + case DHCP_DECLINE: + log_dhcp_server(server, "DECLINE (0x%x)", + be32toh(req->message->xid)); + + /* TODO: make sure we don't offer this address again */ + + return 1; + + break; + case DHCP_REQUEST: + { + be32_t address; + bool init_reboot = false; + int pool_offset; + + /* 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 more carefully if IP is correct */ + address = req->requested_ip; + init_reboot = true; + } 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; + } + + 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) { + free(lease); + return -ENOMEM; + } + lease->client_id.length = req->client_id.length; + memcpy(&lease->chaddr, &req->message->chaddr, ETH_ALEN); + lease->gateway = req->message->giaddr; + } else + lease = existing_lease; + + r = sd_event_now(server->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + time_now = now(clock_boottime_or_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) { + r = server_send_nak(server, req); + if (r < 0) { + /* this only fails on critical errors */ + log_dhcp_server(server, "could not send nak: %s", + strerror(-r)); + return r; + } else { + log_dhcp_server(server, "NAK (0x%x)", + be32toh(req->message->xid)); + return DHCP_NAK; + } + } + + break; + } + case DHCP_RELEASE: { + int pool_offset; + + log_dhcp_server(server, "RELEASE (0x%x)", + be32toh(req->message->xid)); + + if (!existing_lease) + return 0; + + if (existing_lease->address != req->message->ciaddr) + return 0; + + pool_offset = get_pool_offset(server, req->message->ciaddr); + if (pool_offset < 0) + return 0; + + if (server->bound_leases[pool_offset] == existing_lease) { + server->bound_leases[pool_offset] = NULL; + hashmap_remove(server->leases_by_client_id, existing_lease); + dhcp_lease_free(existing_lease); + + return 1; + } else + return 0; + } + } + + return 0; +} + static int server_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_free_ uint8_t *message = NULL; + _cleanup_free_ DHCPMessage *message = NULL; uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))]; sd_dhcp_server *server = userdata; struct iovec iov = {}; @@ -145,6 +852,8 @@ static int server_receive_message(sd_event_source *s, int fd, len = recvmsg(fd, &msg, 0); if (len < buflen) return 0; + else if ((size_t)len < sizeof(DHCPMessage)) + return 0; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IP && @@ -160,9 +869,7 @@ static int server_receive_message(sd_event_source *s, int fd, } } - log_dhcp_server(server, "received message"); - - return 1; + return dhcp_server_handle_message(server, message, (size_t)len); } int sd_dhcp_server_start(sd_dhcp_server *server) { @@ -171,7 +878,17 @@ int sd_dhcp_server_start(sd_dhcp_server *server) { assert_return(server, -EINVAL); assert_return(server->event, -EINVAL); assert_return(!server->receive_message, -EBUSY); + assert_return(server->fd_raw == -1, -EBUSY); assert_return(server->fd == -1, -EBUSY); + assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH); + + r = socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0); + if (r < 0) { + r = -errno; + sd_dhcp_server_stop(server); + return r; + } + server->fd_raw = r; r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER); if (r < 0) { @@ -199,3 +916,28 @@ int sd_dhcp_server_start(sd_dhcp_server *server) { return 0; } + +int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { + unsigned i; + int r; + + assert_return(server, -EINVAL); + assert(server->bound_leases); + + for (i = 0; i < server->pool_size; i++) { + DHCPLease *lease = server->bound_leases[i]; + + if (!lease) + continue; + + r = server_send_forcerenew(server, lease->address, + lease->gateway, + lease->chaddr); + if (r < 0) + return r; + else + log_dhcp_server(server, "FORCERENEW"); + } + + return r; +}