chiark / gitweb /
sd-dhcp-server: add support for clients requesting lease lifetime
[elogind.git] / src / libsystemd-network / sd-dhcp-server.c
index 994c7beb31980ead257326141162d92f5cd21680..4c43eeb3fb281a711d98d2d1a64c5ab199fb70aa 100644 (file)
 #include "dhcp-server-internal.h"
 #include "dhcp-internal.h"
 
+#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);
@@ -277,6 +294,103 @@ int dhcp_server_send_packet(sd_dhcp_server *server,
                                                     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;
+        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 parse_request(uint8_t code, uint8_t len, const uint8_t *option,
                          void *user_data) {
         DHCPRequest *req = user_data;
@@ -284,6 +398,16 @@ static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
         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;
@@ -348,6 +472,9 @@ static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
         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;
 }
 
@@ -377,9 +504,121 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
                 /* this only fails on critical errors */
                 return r;
 
-        log_dhcp_server(server, "received message of type %d", type);
+        switch(type) {
+        case DHCP_DISCOVER:
+        {
+                be32_t address;
+
+                log_dhcp_server(server, "DISCOVER (0x%x)",
+                                be32toh(req->message->xid));
 
-        return 1;
+                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",
+                                        strerror(-r));
+                        return r;
+                } else {
+                        log_dhcp_server(server, "OFFER (0x%x)",
+                                        be32toh(req->message->xid));
+                        return DHCP_OFFER;
+                }
+
+                break;
+        }
+        case DHCP_REQUEST:
+        {
+                be32_t address;
+                bool init_reboot = false;
+
+                /* 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;
+                }
+
+                /* 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 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;
+        }
+        }
+
+        return 0;
 }
 
 static int server_receive_message(sd_event_source *s, int fd,