dhcp: rework static lease logic
authorHans Dedecker <dedeckeh@gmail.com>
Tue, 12 Feb 2019 15:07:24 +0000 (16:07 +0100)
committerHans Dedecker <dedeckeh@gmail.com>
Mon, 18 Feb 2019 10:43:09 +0000 (11:43 +0100)
Rework the static lease logic as the existing logic had different issues.
Static leases are now added in a vlist tree which makes it easier to handle
static lease config changes.
For both DHCPv4 and DHCPv6 static assignments are now created upon the
receival of DHCPv4/DHCPv6 messages as before the static assignment was
created even if the client was not physically present.
In case a hostname is specified in a static lease it won't be overriden
anymore by the hostname received from the client.

Signed-off-by: Hans Dedecker <dedeckeh@gmail.com>
src/config.c
src/dhcpv4.c
src/dhcpv6-ia.c
src/odhcpd.h

index f4a0dcfc920d45f853bdc1f780cf0156d99376a3..d857f24b53c73a657526fa6a71aa05e88b5ef93e 100644 (file)
 #include <libubox/utils.h>
 #include <libubox/avl.h>
 #include <libubox/avl-cmp.h>
+#include <libubox/list.h>
+#include <libubox/vlist.h>
 
 #include "odhcpd.h"
 
 static struct blob_buf b;
 static int reload_pipe[2];
-struct list_head leases = LIST_HEAD_INIT(leases);
+
+static int lease_cmp(const void *k1, const void *k2, void *ptr);
+static void lease_update(struct vlist_tree *tree, struct vlist_node *node_new,
+                        struct vlist_node *node_old);
+
+struct vlist_tree leases = VLIST_TREE_INIT(leases, lease_cmp, lease_update, true, false);
 AVL_TREE(interfaces, avl_strcmp, false, NULL);
 struct config config = {.legacy = false, .main_dhcpv4 = false,
                        .dhcp_cb = NULL, .dhcp_statefile = NULL,
@@ -199,15 +206,6 @@ static int mkdir_p(char *dir, mode_t mask)
        return ret;
 }
 
-static void free_lease(struct lease *l)
-{
-       if (l->head.next)
-               list_del(&l->head);
-
-       free(l->duid);
-       free(l);
-}
-
 static void set_interface_defaults(struct interface *iface)
 {
        iface->learn_routes = 1;
@@ -329,56 +327,64 @@ err:
        return -1;
 }
 
+static void free_lease(struct lease *l)
+{
+       free(l->hostname);
+       free(l);
+}
+
 static int set_lease(struct uci_section *s)
 {
        struct blob_attr *tb[LEASE_ATTR_MAX], *c;
+       struct lease *l;
+       size_t duidlen = 0;
+       uint8_t *duid;
 
        blob_buf_init(&b, 0);
        uci_to_blob(&b, s, &lease_attr_list);
        blobmsg_parse(lease_attrs, LEASE_ATTR_MAX, tb, blob_data(b.head), blob_len(b.head));
 
-       size_t hostlen = 1;
-       if ((c = tb[LEASE_ATTR_NAME]))
-               hostlen = blobmsg_data_len(c);
+       if ((c = tb[LEASE_ATTR_DUID]))
+               duidlen = (blobmsg_data_len(c) - 1) / 2;
 
-       struct lease *lease = calloc(1, sizeof(*lease) + hostlen);
-       if (!lease)
+       l = calloc_a(sizeof(*l), &duid, duidlen);
+       if (!l)
                goto err;
 
-       if (hostlen > 1) {
-               memcpy(lease->hostname, blobmsg_get_string(c), hostlen);
-               if (!odhcpd_valid_hostname(lease->hostname))
-                       goto err;
-       }
-
-       if ((c = tb[LEASE_ATTR_IP]))
-               if (inet_pton(AF_INET, blobmsg_get_string(c), &lease->ipaddr) < 0)
-                       goto err;
-
        if ((c = tb[LEASE_ATTR_MAC]))
-               if (!ether_aton_r(blobmsg_get_string(c), &lease->mac))
+               if (!ether_aton_r(blobmsg_get_string(c), &l->mac))
                        goto err;
 
        if ((c = tb[LEASE_ATTR_DUID])) {
-               size_t duidlen = (blobmsg_data_len(c) - 1) / 2;
-               lease->duid = malloc(duidlen);
-               if (!lease->duid)
-                       goto err;
+               ssize_t len;
 
-               ssize_t len = odhcpd_unhexlify(lease->duid,
-                               duidlen, blobmsg_get_string(c));
+               l->duid = duid;
+               len = odhcpd_unhexlify(l->duid, duidlen, blobmsg_get_string(c));
 
                if (len < 0)
                        goto err;
 
-               lease->duid_len = len;
+               l->duid_len = len;
+       }
+
+       if ((c = tb[LEASE_ATTR_NAME])) {
+               l->hostname = strdup(blobmsg_get_string(c));
+               if (!l->hostname || !odhcpd_valid_hostname(l->hostname))
+                       goto err;
        }
 
+       if ((c = tb[LEASE_ATTR_IP]))
+               if (inet_pton(AF_INET, blobmsg_get_string(c), &l->ipaddr) < 0)
+                       goto err;
+
        if ((c = tb[LEASE_ATTR_HOSTID])) {
                errno = 0;
-               lease->hostid = strtoul(blobmsg_get_string(c), NULL, 16);
+               l->hostid = strtoul(blobmsg_get_string(c), NULL, 16);
                if (errno)
                        goto err;
+       } else {
+               uint32_t i4a = ntohl(l->ipaddr) & 0xff;
+               l->hostid = ((i4a / 100) << 8) | (((i4a % 100) / 10) << 4) | (i4a % 10);
        }
 
        if ((c = tb[LEASE_ATTR_LEASETIME])) {
@@ -386,15 +392,16 @@ static int set_lease(struct uci_section *s)
                if (time < 0)
                        goto err;
 
-               lease->dhcpv4_leasetime = time;
+               l->leasetime = time;
        }
 
-       list_add(&lease->head, &leases);
+       INIT_LIST_HEAD(&l->assignments);
+       vlist_add(&leases, &l->node, l);
        return 0;
 
 err:
-       if (lease)
-               free_lease(lease);
+       if (l)
+               free_lease(l);
 
        return -1;
 }
@@ -775,17 +782,171 @@ static int set_interface(struct uci_section *s)
        return config_parse_interface(blob_data(b.head), blob_len(b.head), s->e.name, true);
 }
 
+static void lease_delete_assignments(struct lease *l, bool v6)
+{
+       struct dhcp_assignment *a, *tmp;
+       unsigned int flag = v6 ? OAF_DHCPV6 : OAF_DHCPV4;
+
+       list_for_each_entry_safe(a, tmp, &l->assignments, lease_list) {
+               if (a->flags & flag)
+                       v6 ? dhcpv6_ia_free_assignment(a) : dhcpv4_free_assignment(a);
+       }
+}
+
+static void lease_update_assignments(struct lease *l)
+{
+       struct dhcp_assignment *a;
+
+       list_for_each_entry(a, &l->assignments, lease_list) {
+               if (a->hostname)
+                       free(a->hostname);
+               a->hostname = NULL;
+
+               if (l->hostname)
+                       a->hostname = strdup(l->hostname);
+
+               a->leasetime = l->leasetime;
+       }
+}
+
+static int lease_cmp(const void *k1, const void *k2, _unused void *ptr)
+{
+       const struct lease *l1 = k1, *l2 = k2;
+       int cmp = 0;
+
+       if (l1->duid_len != l2->duid_len)
+               return l1->duid_len - l2->duid_len;
+
+       if (l1->duid_len && l2->duid_len)
+               cmp = memcmp(l1->duid, l2->duid, l1->duid_len);
+
+       if (cmp)
+               return cmp;
+
+       return memcmp(l1->mac.ether_addr_octet, l2->mac.ether_addr_octet,
+                     sizeof(l1->mac.ether_addr_octet));
+}
+
+static void lease_change_config(struct lease *l_old, struct lease *l_new)
+{
+       bool update = false;
+
+       if ((!!l_new->hostname != !!l_old->hostname) ||
+           (l_new->hostname && strcmp(l_new->hostname, l_old->hostname))) {
+               free(l_old->hostname);
+               l_old->hostname = NULL;
+
+               if (l_new->hostname)
+                       l_old->hostname = strdup(l_new->hostname);
+
+               update = true;
+       }
+
+       if (l_old->leasetime != l_new->leasetime) {
+               l_old->leasetime = l_new->leasetime;
+               update = true;
+       }
+
+       if (l_old->ipaddr != l_new->ipaddr) {
+               l_old->ipaddr = l_new->ipaddr;
+               lease_delete_assignments(l_old, false);
+       }
+
+       if (l_old->hostid != l_new->hostid) {
+               l_old->hostid = l_new->hostid;
+               lease_delete_assignments(l_old, true);
+       }
+
+       if (update)
+               lease_update_assignments(l_old);
+
+       free_lease(l_new);
+}
+
+static void lease_delete(struct lease *l)
+{
+       struct dhcp_assignment *a;
+
+       list_for_each_entry(a, &l->assignments, lease_list) {
+               if (a->flags & OAF_DHCPV6)
+                       dhcpv6_ia_free_assignment(a);
+               else if (a->flags & OAF_DHCPV4)
+                       dhcpv4_free_assignment(a);
+       }
+
+       free_lease(l);
+}
+
+static void lease_update(_unused struct vlist_tree *tree, struct vlist_node *node_new,
+                        struct vlist_node *node_old)
+{
+       struct lease *lease_new = container_of(node_new, struct lease, node);
+       struct lease *lease_old = container_of(node_old, struct lease, node);
+
+       if (node_old && node_new)
+               lease_change_config(lease_old, lease_new);
+       else if (node_old)
+               lease_delete(lease_old);
+}
+
+struct lease *config_find_lease_by_duid(const uint8_t *duid, const uint16_t len)
+{
+       struct lease *l;
+
+       vlist_for_each_element(&leases, l, node) {
+               if (l->duid_len == len && !memcmp(l->duid, duid, len))
+                       return l;
+       }
+
+       return NULL;
+}
+
+struct lease *config_find_lease_by_mac(const uint8_t *mac)
+{
+       struct lease *l;
+
+       vlist_for_each_element(&leases, l, node) {
+               if (!memcmp(l->mac.ether_addr_octet, mac,
+                           sizeof(l->mac.ether_addr_octet)))
+                       return l;
+       }
+
+       return NULL;
+}
+
+struct lease *config_find_lease_by_hostid(const uint32_t hostid)
+{
+       struct lease *l;
+
+       vlist_for_each_element(&leases, l, node) {
+               if (l->hostid == hostid)
+                       return l;
+       }
+
+       return NULL;
+}
+
+struct lease *config_find_lease_by_ipaddr(const uint32_t ipaddr)
+{
+       struct lease *l;
+
+       vlist_for_each_element(&leases, l, node) {
+               if (l->ipaddr == ipaddr)
+                       return l;
+       }
+
+       return NULL;
+}
+
 void odhcpd_reload(void)
 {
        struct uci_context *uci = uci_alloc_context();
        struct interface *master = NULL, *i, *tmp;
 
-       while (!list_empty(&leases))
-               free_lease(list_first_entry(&leases, struct lease, head));
-
        if (!uci)
                return;
 
+       vlist_update(&leases);
        avl_for_each_element(&interfaces, i, avl)
                clean_interface(i);
 
@@ -814,6 +975,8 @@ void odhcpd_reload(void)
                free(path);
        }
 
+       vlist_flush(&leases);
+
 #ifdef WITH_UBUS
        ubus_apply_network();
 #endif
@@ -937,4 +1100,3 @@ void odhcpd_run(void)
        odhcpd_reload();
        uloop_run();
 }
-
index 00e4c3fe0a6ce8c9eb43037f2252c6eee470b941..da2c051e46656e329712d5a253faad9884458723 100644 (file)
 
 static void dhcpv4_netevent_cb(unsigned long event, struct netevent_handler_info *info);
 static int setup_dhcpv4_addresses(struct interface *iface);
-static void update_static_assignments(struct interface *iface);
 static bool addr_is_fr_ip(struct interface *iface, struct in_addr *addr);
 static void valid_until_cb(struct uloop_timeout *event);
 static void handle_addrlist_change(struct interface *iface);
-static void free_dhcpv4_assignment(struct dhcp_assignment *a);
 static void dhcpv4_fr_start(struct dhcp_assignment *a);
 static void dhcpv4_fr_rand_delay(struct dhcp_assignment *a);
 static void dhcpv4_fr_stop(struct dhcp_assignment *a);
@@ -158,11 +156,9 @@ int dhcpv4_setup_interface(struct interface *iface, bool enable)
 
                iface->dhcpv4_event.handle_dgram = handle_dhcpv4;
                odhcpd_register(&iface->dhcpv4_event);
-
-               update_static_assignments(iface);
        } else if (iface->dhcpv4_assignments.next) {
                while (!list_empty(&iface->dhcpv4_assignments))
-                       free_dhcpv4_assignment(list_first_entry(&iface->dhcpv4_assignments,
+                       dhcpv4_free_assignment(list_first_entry(&iface->dhcpv4_assignments,
                                                        struct dhcp_assignment, head));
        }
 
@@ -206,17 +202,6 @@ static struct dhcp_assignment *find_assignment_by_hwaddr(struct interface *iface
        return NULL;
 }
 
-static struct dhcp_assignment *find_assignment_by_addr(struct interface *iface, const uint32_t addr)
-{
-       struct dhcp_assignment *a;
-
-       list_for_each_entry(a, &iface->dhcpv4_assignments, head)
-               if (a->addr == addr)
-                       return a;
-
-       return NULL;
-}
-
 static int setup_dhcpv4_addresses(struct interface *iface)
 {
        iface->dhcpv4_start_ip.s_addr = INADDR_ANY;
@@ -291,68 +276,6 @@ static int setup_dhcpv4_addresses(struct interface *iface)
        return 0;
 }
 
-static void update_static_assignments(struct interface *iface)
-{
-       struct dhcp_assignment *a, *c;
-
-       /* Cleanup static entries not belonging to the network */
-       list_for_each_entry_safe(a, c, &iface->dhcpv4_assignments, head) {
-               if ((a->flags & OAF_STATIC) &&
-                               ((a->addr & iface->dhcpv4_mask.s_addr) !=
-                                (iface->dhcpv4_start.s_addr & iface->dhcpv4_mask.s_addr)))
-                       free_dhcpv4_assignment(a);
-       }
-
-       /* Parse static entries */
-       struct lease *lease;
-       list_for_each_entry(lease, &leases, head) {
-               if ((iface->dhcpv4_start_ip.s_addr & iface->dhcpv4_mask.s_addr) !=
-                               (lease->ipaddr.s_addr & iface->dhcpv4_mask.s_addr)) {
-                       continue;
-               }
-
-               a = find_assignment_by_hwaddr(iface, lease->mac.ether_addr_octet);
-
-               if (!a) {
-                       /* Check if there's an assignment with the specified IP address */
-                       if (find_assignment_by_addr(iface, lease->ipaddr.s_addr))
-                               continue;
-
-                       /* Construct entry */
-                       a = calloc(1, sizeof(*a));
-                       if (!a) {
-                               syslog(LOG_ERR, "Calloc failed for static lease on %s", iface->name);
-                               continue;
-                       }
-                       memcpy(a->hwaddr, lease->mac.ether_addr_octet, sizeof(a->hwaddr));
-               }
-
-               a->leasetime = lease->dhcpv4_leasetime;
-
-               a->addr = lease->ipaddr.s_addr;
-               /* Static assignment */
-               a->flags |= OAF_STATIC;
-               /* Infinite valid */
-               a->valid_until = 0;
-               a->iface = iface;
-               if (lease->hostname[0]) {
-                       free(a->hostname);
-                       a->hostname = strdup(lease->hostname);
-               }
-
-               /* Assign to all interfaces */
-               list_for_each_entry(c, &iface->dhcpv4_assignments, head) {
-                       if (ntohl(c->addr) > ntohl(a->addr)) {
-                               list_add_tail(&a->head, &c->head);
-                               break;
-                       }
-               }
-
-               if (&c->head == &iface->dhcpv4_assignments)
-                       list_add(&a->head, &iface->dhcpv4_assignments);
-       }
-}
-
 static void inc_ref_cnt_ip(struct odhcpd_ref_ip **ptr, struct odhcpd_ref_ip *ip)
 {
        *ptr = ip;
@@ -422,7 +345,7 @@ static void valid_until_cb(struct uloop_timeout *event)
                struct dhcp_assignment *a, *n;
                list_for_each_entry_safe(a, n, &iface->dhcpv4_assignments, head) {
                        if (!INFINITE_VALID(a->valid_until) && a->valid_until < now)
-                               free_dhcpv4_assignment(a);
+                               dhcpv4_free_assignment(a);
                }
        }
        uloop_timeout_set(event, 1000);
@@ -495,11 +418,14 @@ static char *dhcpv4_msg_to_string(uint8_t reqmsg)
        }
 }
 
-static void free_dhcpv4_assignment(struct dhcp_assignment *a)
+void dhcpv4_free_assignment(struct dhcp_assignment *a)
 {
        if (a->head.next)
                list_del(&a->head);
 
+       if (a->lease_list.next)
+               list_del(&a->lease_list);
+
        if (a->fr_ip)
                dhcpv4_fr_stop(a);
 
@@ -665,15 +591,15 @@ static void dhcpv4_fr_stop(struct dhcp_assignment *a)
 static void handle_dhcpv4(void *addr, void *data, size_t len,
                struct interface *iface, _unused void *dest_addr)
 {
+       struct dhcpv4_message *req = data;
+
        if (!iface->dhcpv4)
                return;
 
-       struct dhcpv4_message *req = data;
        if (len < offsetof(struct dhcpv4_message, options) + 4 ||
                        req->op != DHCPV4_BOOTREQUEST || req->hlen != 6)
                return;
 
-
        syslog(LOG_NOTICE, "Got DHCPv4 request on %s", iface->name);
 
        if (!iface->dhcpv4_start_ip.s_addr && !iface->dhcpv4_end_ip.s_addr) {
@@ -749,20 +675,20 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
        }
 
        if (reqmsg != DHCPV4_MSG_DISCOVER && reqmsg != DHCPV4_MSG_REQUEST &&
-                       reqmsg != DHCPV4_MSG_INFORM && reqmsg != DHCPV4_MSG_DECLINE &&
-                       reqmsg != DHCPV4_MSG_RELEASE)
+           reqmsg != DHCPV4_MSG_INFORM && reqmsg != DHCPV4_MSG_DECLINE &&
+           reqmsg != DHCPV4_MSG_RELEASE)
                return;
 
-       struct dhcp_assignment *lease = NULL;
+       struct dhcp_assignment *a = NULL;
        uint32_t serverid = iface->dhcpv4_local.s_addr;
        uint32_t fr_serverid = INADDR_ANY;
 
        if (reqmsg != DHCPV4_MSG_INFORM)
-               lease = dhcpv4_lease(iface, reqmsg, req->chaddr, reqaddr,
-                                       &leasetime, hostname, hostname_len,
-                                       accept_fr_nonce, &incl_fr_opt, &fr_serverid);
+               a = dhcpv4_lease(iface, reqmsg, req->chaddr, reqaddr,
+                                &leasetime, hostname, hostname_len,
+                                accept_fr_nonce, &incl_fr_opt, &fr_serverid);
 
-       if (!lease) {
+       if (!a) {
                if (reqmsg == DHCPV4_MSG_REQUEST)
                        msg = DHCPV4_MSG_NAK;
                else if (reqmsg == DHCPV4_MSG_DISCOVER)
@@ -770,8 +696,8 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
        } else if (reqmsg == DHCPV4_MSG_DISCOVER)
                msg = DHCPV4_MSG_OFFER;
        else if (reqmsg == DHCPV4_MSG_REQUEST &&
-                       ((reqaddr && reqaddr != lease->addr) ||
-                        (req->ciaddr.s_addr && req->ciaddr.s_addr != lease->addr))) {
+                       ((reqaddr && reqaddr != a->addr) ||
+                        (req->ciaddr.s_addr && req->ciaddr.s_addr != a->addr))) {
                msg = DHCPV4_MSG_NAK;
                /*
                 * DHCP client requested an IP which we can't offer to him. Probably the
@@ -814,10 +740,10 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
        dhcpv4_put(&reply, &cookie, DHCPV4_OPT_MESSAGE, 1, &msg);
        dhcpv4_put(&reply, &cookie, DHCPV4_OPT_SERVERID, 4, &serverid);
 
-       if (lease) {
+       if (a) {
                uint32_t val;
 
-               reply.yiaddr.s_addr = lease->addr;
+               reply.yiaddr.s_addr = a->addr;
 
                val = htonl(leasetime);
                dhcpv4_put(&reply, &cookie, DHCPV4_OPT_LEASETIME, 4, &val);
@@ -833,9 +759,9 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
                dhcpv4_put(&reply, &cookie, DHCPV4_OPT_NETMASK, 4,
                                &iface->dhcpv4_mask.s_addr);
 
-               if (lease->hostname)
+               if (a->hostname)
                        dhcpv4_put(&reply, &cookie, DHCPV4_OPT_HOSTNAME,
-                                       strlen(lease->hostname), lease->hostname);
+                                       strlen(a->hostname), a->hostname);
 
                if (iface->dhcpv4_bcast.s_addr != INADDR_ANY)
                        dhcpv4_put(&reply, &cookie, DHCPV4_OPT_BROADCAST, 4, &iface->dhcpv4_bcast);
@@ -851,7 +777,7 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
                                        .key = {0},
                                };
 
-                               memcpy(auth.key, lease->key, sizeof(auth.key));
+                               memcpy(auth.key, a->key, sizeof(auth.key));
                                dhcpv4_put(&reply, &cookie, DHCPV4_OPT_AUTHENTICATION, sizeof(auth), &auth);
                        } else {
                                uint8_t one = 1;
@@ -966,60 +892,89 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
 #endif
 }
 
-static bool dhcpv4_assign(struct interface *iface,
-               struct dhcp_assignment *assign, uint32_t raddr)
+static bool dhcpv4_assign(struct interface *iface, struct dhcp_assignment *a,
+                         uint32_t raddr)
 {
+       struct dhcp_assignment *c;
        uint32_t start = ntohl(iface->dhcpv4_start_ip.s_addr);
        uint32_t end = ntohl(iface->dhcpv4_end_ip.s_addr);
        uint32_t count = end - start + 1;
+       uint32_t seed = 0;
+
+       /* Preconfigured IP address by static lease */
+       if (a->addr) {
+               if (list_empty(&iface->dhcpv4_assignments)) {
+                       list_add(&a->head, &iface->dhcpv4_assignments);
+                       return true;
+               }
+
+               list_for_each_entry(c, &iface->dhcpv4_assignments, head) {
+                       if (ntohl(c->addr) > ntohl(a->addr)) {
+                               list_add_tail(&a->head, &c->head);
+                               return true;
+                       } else if (ntohl(a->addr) == ntohl(c->addr))
+                               return false;
+               }
+       }
 
        /* try to assign the IP the client asked for */
        if (start <= ntohl(raddr) && ntohl(raddr) <= end &&
-                       !find_assignment_by_addr(iface, raddr)) {
-               assign->addr = raddr;
-               syslog(LOG_INFO, "Assigning the IP the client asked for: %u.%u.%u.%u",
-                               ((uint8_t *)&assign->addr)[0],
-                               ((uint8_t *)&assign->addr)[1],
-                               ((uint8_t *)&assign->addr)[2],
-                               ((uint8_t *)&assign->addr)[3]);
-               return true;
+           !config_find_lease_by_ipaddr(raddr)) {
+               list_for_each_entry(c, &iface->dhcpv4_assignments, head) {
+                       if (ntohl(c->addr) > ntohl(raddr)) {
+                               list_add_tail(&a->head, &c->head);
+                               a->addr = raddr;
+
+                               syslog(LOG_INFO, "Assigning the IP the client asked for: %u.%u.%u.%u",
+                                      ((uint8_t *)&a->addr)[0], ((uint8_t *)&a->addr)[1],
+                                      ((uint8_t *)&a->addr)[2], ((uint8_t *)&a->addr)[3]);
+
+                               return true;
+                       } else if (ntohl(raddr) == ntohl(c->addr))
+                               break;
+               }
        }
 
        /* Seed RNG with checksum of hwaddress */
-       uint32_t seed = 0;
-       for (size_t i = 0; i < sizeof(assign->hwaddr); ++i) {
+       for (size_t i = 0; i < sizeof(a->hwaddr); ++i) {
                /* Knuth's multiplicative method */
-               uint8_t o = assign->hwaddr[i];
+               uint8_t o = a->hwaddr[i];
                seed += (o*2654435761) % UINT32_MAX;
        }
 
        srand(seed);
 
-       uint32_t try = (((uint32_t)rand()) % count) + start;
+       for (uint32_t i = 0, try = (((uint32_t)rand()) % count) + start; i < count;
+            ++i, try = (((try - start) + 1) % count) + start) {
 
-       if (list_empty(&iface->dhcpv4_assignments)) {
-               assign->addr = htonl(try);
-               syslog(LOG_INFO, "Assigning mapped IP (empty list): %u.%u.%u.%u",
-                               ((uint8_t *)&assign->addr)[0],
-                               ((uint8_t *)&assign->addr)[1],
-                               ((uint8_t *)&assign->addr)[2],
-                               ((uint8_t *)&assign->addr)[3]);
-               return true;
-       }
+               if (config_find_lease_by_ipaddr(try))
+                       continue;
+
+               if (list_empty(&iface->dhcpv4_assignments)) {
+                       list_add(&a->head, &iface->dhcpv4_assignments);
+                       a->addr = htonl(try);
+
+                       syslog(LOG_INFO, "Assigning mapped IP (empty list): %u.%u.%u.%u",
+                              ((uint8_t *)&a->addr)[0], ((uint8_t *)&a->addr)[1],
+                              ((uint8_t *)&a->addr)[2], ((uint8_t *)&a->addr)[3]);
 
-       for (uint32_t i = 0; i < count; ++i) {
-               if (!find_assignment_by_addr(iface, htonl(try))) {
-                       /* test was successful: IP address is not assigned, assign it */
-                       assign->addr = htonl(try);
-                       syslog(LOG_DEBUG, "Assigning mapped IP: %u.%u.%u.%u (try %u of %u)",
-                                       ((uint8_t *)&assign->addr)[0],
-                                       ((uint8_t *)&assign->addr)[1],
-                                       ((uint8_t *)&assign->addr)[2],
-                                       ((uint8_t *)&assign->addr)[3],
-                                       i, count);
                        return true;
                }
-               try = (((try - start) + 1) % count) + start;
+
+               list_for_each_entry(c, &iface->dhcpv4_assignments, head) {
+                       if (ntohl(c->addr) > try) {
+                               list_add_tail(&a->head, &c->head);
+                               a->addr = htonl(try);
+
+                               syslog(LOG_DEBUG, "Assigning mapped IP: %u.%u.%u.%u (try %u of %u)",
+                                       ((uint8_t *)&a->addr)[0], ((uint8_t *)&a->addr)[1],
+                                       ((uint8_t *)&a->addr)[2], ((uint8_t *)&a->addr)[3],
+                                       i, count);
+
+                               return true;
+                       } else if (try == ntohl(c->addr))
+                               break;
+               }
        }
 
        syslog(LOG_WARNING, "Can't assign any IP address -> address space is full");
@@ -1027,15 +982,21 @@ static bool dhcpv4_assign(struct interface *iface,
 }
 
 
-static struct dhcp_assignment* dhcpv4_lease(struct interface *iface,
-               enum dhcpv4_msg msg, const uint8_t *mac, const uint32_t reqaddr,
-               uint32_t *leasetime, const char *hostname, const size_t hostname_len,
-               const bool accept_fr_nonce, bool *incl_fr_opt, uint32_t *fr_serverid)
+static struct dhcp_assignment*
+dhcpv4_lease(struct interface *iface, enum dhcpv4_msg msg, const uint8_t *mac,
+            const uint32_t reqaddr, uint32_t *leasetime, const char *hostname,
+            const size_t hostname_len, const bool accept_fr_nonce, bool *incl_fr_opt,
+            uint32_t *fr_serverid)
 {
        struct dhcp_assignment *a = find_assignment_by_hwaddr(iface, mac);
-       struct dhcp_assignment *lease = NULL;
+       struct lease *l = config_find_lease_by_mac(mac);
        time_t now = odhcpd_time();
 
+       if (l && a && a->lease != l) {
+               dhcpv4_free_assignment(a);
+               a = NULL;
+       }
+
        if (a && (a->flags & OAF_BOUND) && a->fr_ip) {
                *fr_serverid = a->fr_ip->addr.addr.in.s_addr;
                dhcpv4_fr_stop(a);
@@ -1045,7 +1006,7 @@ static struct dhcp_assignment* dhcpv4_lease(struct interface *iface,
                bool assigned = !!a;
 
                if (!a) {
-                       if (!iface->no_dynamic_dhcp) {
+                       if (!iface->no_dynamic_dhcp || l) {
                                /* Create new binding */
                                a = calloc(1, sizeof(*a));
                                if (!a) {
@@ -1054,22 +1015,35 @@ static struct dhcp_assignment* dhcpv4_lease(struct interface *iface,
                                        return NULL;
                                }
                                memcpy(a->hwaddr, mac, sizeof(a->hwaddr));
-                               /* Don't consider new assignment as infinite */
-                               a->valid_until = now;
+                               /* Set valid time to 0 for static lease indicating */
+                               /* infinite lifetime otherwise current time        */
+                               a->valid_until = l ? 0 : now;
+                               a->iface = iface;
+                               a->flags = OAF_DHCPV4;
+                               a->addr = l ? l->ipaddr : INADDR_ANY;
 
                                assigned = dhcpv4_assign(iface, a, reqaddr);
-                               if (assigned) {
-                                       a->iface = iface;
-                                       list_add(&a->head, &iface->dhcpv4_assignments);
+
+                               if (l) {
+                                       a->flags |= OAF_STATIC;
+
+                                       if (l->hostname)
+                                               a->hostname = strdup(l->hostname);
+
+                                       if (l->leasetime)
+                                               a->leasetime = l->leasetime;
+
+                                       list_add(&a->lease_list, &l->assignments);
+                                       a->lease = l;
                                }
                        }
-               } else if ((a->addr & iface->dhcpv4_mask.s_addr) !=
-                               (iface->dhcpv4_start_ip.s_addr & iface->dhcpv4_mask.s_addr)) {
+               } else if (((a->addr & iface->dhcpv4_mask.s_addr) !=
+                           (iface->dhcpv4_start_ip.s_addr & iface->dhcpv4_mask.s_addr)) &&
+                           !(a->flags & OAF_STATIC)) {
                        list_del(&a->head);
+                       a->addr = INADDR_ANY;
 
                        assigned = dhcpv4_assign(iface, a, reqaddr);
-                       if (assigned)
-                               list_add(&a->head, &iface->dhcpv4_assignments);
                }
 
                if (assigned) {
@@ -1090,7 +1064,7 @@ static struct dhcp_assignment* dhcpv4_lease(struct interface *iface,
                                if (!(a->flags & OAF_STATIC))
                                        a->valid_until = now;
                        } else {
-                               if (hostname_len > 0) {
+                               if ((!(a->flags & OAF_STATIC) || !a->hostname) && hostname_len > 0) {
                                        a->hostname = realloc(a->hostname, hostname_len + 1);
                                        if (a->hostname) {
                                                memcpy(a->hostname, hostname, hostname_len);
@@ -1116,12 +1090,10 @@ static struct dhcp_assignment* dhcpv4_lease(struct interface *iface,
                        }
                } else if (!assigned && a) {
                        /* Cleanup failed assignment */
-                       free_dhcpv4_assignment(a);
+                       dhcpv4_free_assignment(a);
                        a = NULL;
                }
 
-               if (assigned && a)
-                       lease = a;
        } else if (msg == DHCPV4_MSG_RELEASE && a) {
                a->flags &= ~OAF_BOUND;
 
@@ -1139,5 +1111,5 @@ static struct dhcp_assignment* dhcpv4_lease(struct interface *iface,
 
        dhcpv6_ia_write_statefile();
 
-       return lease;
+       return a;
 }
index 7c3ccad5b0d219ab8e95349481251ac4901c6c38..4cc6fab8a226272bae4681edef71655258221189 100644 (file)
@@ -40,7 +40,6 @@
      (addrs)[(i)].prefix > 64)
 
 static void dhcpv6_netevent_cb(unsigned long event, struct netevent_handler_info *info);
-static void free_dhcpv6_assignment(struct dhcp_assignment *c);
 static void set_border_assignment_size(struct interface *iface, struct dhcp_assignment *b);
 static void handle_addrlist_change(struct netevent_handler_info *info);
 static void start_reconf(struct dhcp_assignment *a);
@@ -68,13 +67,12 @@ int dhcpv6_ia_setup_interface(struct interface *iface, bool enable)
 
                while (!list_empty(&iface->ia_assignments)) {
                        c = list_first_entry(&iface->ia_assignments, struct dhcp_assignment, head);
-                       free_dhcpv6_assignment(c);
+                       dhcpv6_ia_free_assignment(c);
                }
        }
 
        if (enable && iface->dhcpv6 == MODE_SERVER) {
                struct dhcp_assignment *border;
-               struct lease *lease;
 
                if (!iface->ia_assignments.next)
                        INIT_LIST_HEAD(&iface->ia_assignments);
@@ -93,57 +91,6 @@ int dhcpv6_ia_setup_interface(struct interface *iface, bool enable)
                        border = list_last_entry(&iface->ia_assignments, struct dhcp_assignment, head);
 
                set_border_assignment_size(iface, border);
-
-               /* Parse static entries */
-               list_for_each_entry(lease, &leases, head) {
-                       /* Construct entry */
-                       size_t duid_len = lease->duid_len ? lease->duid_len : 14;
-                       struct dhcp_assignment *a = calloc(1, sizeof(*a) + duid_len);
-                       if (!a) {
-                               syslog(LOG_ERR, "Calloc failed for static lease assignment on %s",
-                                       iface->name);
-                               return -1;
-                       }
-
-                       a->leasetime = lease->dhcpv4_leasetime;
-
-                       a->clid_len = duid_len;
-                       a->length = 128;
-                       if (lease->hostid) {
-                               a->assigned = lease->hostid;
-                       } else {
-                               uint32_t i4a = ntohl(lease->ipaddr.s_addr) & 0xff;
-                               a->assigned = ((i4a / 100) << 8) | (((i4a % 100) / 10) << 4) | (i4a % 10);
-                       }
-
-                       odhcpd_urandom(a->key, sizeof(a->key));
-                       memcpy(a->clid_data, lease->duid, lease->duid_len);
-                       memcpy(a->mac, lease->mac.ether_addr_octet, sizeof(a->mac));
-                       /* Static assignment */
-                       a->flags |= OAF_STATIC;
-                       /* Infinite valid */
-                       a->valid_until = 0;
-
-                       /* Assign to all interfaces */
-                       struct dhcp_assignment *c;
-                       list_for_each_entry(c, &iface->ia_assignments, head) {
-                               if (c->length != 128 || c->assigned > a->assigned) {
-                                       list_add_tail(&a->head, &c->head);
-                                       break;
-                               } else if (c->assigned == a->assigned)
-                                       /* Already an assignment with that number */
-                                       break;
-                       }
-
-                       if (a->head.next) {
-                               a->iface = iface;
-                               if (lease->hostname[0]) {
-                                       free(a->hostname);
-                                       a->hostname = strdup(lease->hostname);
-                               }
-                       } else
-                               free_dhcpv6_assignment(a);
-               }
        }
        return 0;
 }
@@ -166,24 +113,6 @@ static void dhcpv6_netevent_cb(unsigned long event, struct netevent_handler_info
 }
 
 
-static void free_dhcpv6_assignment(struct dhcp_assignment *c)
-{
-       if (c->managed_sock.fd.registered) {
-               ustream_free(&c->managed_sock.stream);
-               close(c->managed_sock.fd.fd);
-       }
-
-       if (c->head.next)
-               list_del(&c->head);
-
-       if (c->reconf_cnt)
-               stop_reconf(c);
-
-       free(c->managed);
-       free(c->hostname);
-       free(c);
-}
-
 static inline bool valid_prefix_length(const struct dhcp_assignment *a, const uint8_t prefix_length)
 {
        return (a->managed_size || a->length > prefix_length);
@@ -272,8 +201,29 @@ static int send_reconf(struct dhcp_assignment *assign)
        return odhcpd_send(iface->dhcpv6_event.uloop.fd, &assign->peer, &iov, 1, iface);
 }
 
+void dhcpv6_ia_free_assignment(struct dhcp_assignment *a)
+{
+       if (a->managed_sock.fd.registered) {
+               ustream_free(&a->managed_sock.stream);
+               close(a->managed_sock.fd.fd);
+       }
+
+       if (a->head.next)
+               list_del(&a->head);
+
+       if (a->lease_list.next)
+               list_del(&a->lease_list);
+
+       if (a->reconf_cnt)
+               stop_reconf(a);
+
+       free(a->managed);
+       free(a->hostname);
+       free(a);
+}
+
 void dhcpv6_ia_enum_addrs(struct interface *iface, struct dhcp_assignment *c,
-                               time_t now, dhcpv6_binding_cb_handler_t func, void *arg)
+                         time_t now, dhcpv6_binding_cb_handler_t func, void *arg)
 {
        struct odhcpd_ipaddr *addrs = (c->managed) ? c->managed : iface->addr6;
        size_t addrlen = (c->managed) ? (size_t)c->managed_size : iface->addr6_len;
@@ -584,7 +534,7 @@ static void managed_handle_pd_data(struct ustream *s, _unused int bytes_new)
        }
 
        if (first && c->managed_size == 0)
-               free_dhcpv6_assignment(c);
+               dhcpv6_ia_free_assignment(c);
        else if (first && !(c->flags & OAF_STATIC))
                c->valid_until = now + 150;
 }
@@ -691,12 +641,28 @@ static bool assign_pd(struct interface *iface, struct dhcp_assignment *assign)
        return false;
 }
 
-static bool assign_na(struct interface *iface, struct dhcp_assignment *assign)
+static bool assign_na(struct interface *iface, struct dhcp_assignment *a)
 {
-       /* Seed RNG with checksum of DUID */
+       struct dhcp_assignment *c;
        uint32_t seed = 0;
-       for (size_t i = 0; i < assign->clid_len; ++i)
-               seed += assign->clid_data[i];
+
+       /* Preconfigured assignment by static lease */
+       if (a->assigned) {
+               list_for_each_entry(c, &iface->ia_assignments, head) {
+                       if (c->length == 0)
+                               continue;
+
+                       if (c->assigned > a->assigned || c->length != 128) {
+                               list_add_tail(&a->head, &c->head);
+                               return true;
+                       } else if (c->assigned == a->assigned)
+                               return false;
+               }
+       }
+
+       /* Seed RNG with checksum of DUID */
+       for (size_t i = 0; i < a->clid_len; ++i)
+               seed += a->clid_data[i];
        srand(seed);
 
        /* Try to assign up to 100x */
@@ -704,14 +670,16 @@ static bool assign_na(struct interface *iface, struct dhcp_assignment *assign)
                uint32_t try;
                do try = ((uint32_t)rand()) % 0x0fff; while (try < 0x100);
 
-               struct dhcp_assignment *c;
+               if (config_find_lease_by_hostid(try))
+                       continue;
+
                list_for_each_entry(c, &iface->ia_assignments, head) {
                        if (c->length == 0)
                                continue;
 
                        if (c->assigned > try || c->length != 128) {
-                               assign->assigned = try;
-                               list_add_tail(&assign->head, &c->head);
+                               a->assigned = try;
+                               list_add_tail(&a->head, &c->head);
                                return true;
                        } else if (c->assigned == try)
                                break;
@@ -818,7 +786,7 @@ static void valid_until_cb(struct uloop_timeout *event)
                        if (!INFINITE_VALID(a->valid_until) && a->valid_until < now) {
                                if ((a->length < 128 && a->clid_len > 0) ||
                                                (a->length == 128 && a->clid_len == 0))
-                                       free_dhcpv6_assignment(a);
+                                       dhcpv6_ia_free_assignment(a);
 
                        }
                }
@@ -1160,18 +1128,16 @@ static bool dhcpv6_ia_on_link(const struct dhcpv6_ia_hdr *ia, struct dhcp_assign
 ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *iface,
                const struct sockaddr_in6 *addr, const void *data, const uint8_t *end)
 {
-       time_t now = odhcpd_time();
-       size_t response_len = 0;
+       struct lease *l;
+       struct dhcp_assignment *first = NULL;
        const struct dhcpv6_client_header *hdr = data;
-       uint8_t *start = (uint8_t*)&hdr[1], *odata;
-       uint16_t otype, olen;
-       /* Find and parse client-id and hostname */
-       bool accept_reconf = false;
-       uint8_t *clid_data = NULL, clid_len = 0, mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
-       char hostname[256];
-       size_t hostname_len = 0;
-       bool notonlink = false, rapid_commit = false;
-       char duidbuf[261];
+       time_t now = odhcpd_time();
+       uint16_t otype, olen, clid_len = 0;
+       uint8_t *start = (uint8_t *)&hdr[1], *odata;
+       uint8_t *clid_data = NULL, mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+       size_t hostname_len = 0, response_len = 0;
+       bool notonlink = false, rapid_commit = false, accept_reconf = false;
+       char duidbuf[261], hostname[256];
 
        dhcpv6_for_each_option(start, end, otype, olen, odata) {
                if (otype == DHCPV6_OPT_CLIENTID) {
@@ -1201,7 +1167,10 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
        if (!clid_data || !clid_len || clid_len > 130)
                goto out;
 
-       struct dhcp_assignment *first = NULL;
+       l = config_find_lease_by_duid(clid_data, clid_len);
+       if (!l)
+               l = config_find_lease_by_mac(mac);
+
        dhcpv6_for_each_option(start, end, otype, olen, odata) {
                bool is_pd = (otype == DHCPV6_OPT_IA_PD);
                bool is_na = (otype == DHCPV6_OPT_IA_NA);
@@ -1247,27 +1216,25 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
                /* Find assignment */
                struct dhcp_assignment *c, *a = NULL;
                list_for_each_entry(c, &iface->ia_assignments, head) {
-                       if (((c->clid_len == clid_len && !memcmp(c->clid_data, clid_data, clid_len)) ||
-                                       (c->clid_len >= clid_len && !c->clid_data[0] && !c->clid_data[1]
-                                               && !memcmp(c->mac, mac, sizeof(mac)))) &&
-                                       (!(c->flags & (OAF_BOUND|OAF_TENTATIVE)) || c->iaid == ia->iaid) &&
-                                       (INFINITE_VALID(c->valid_until) || now < c->valid_until) &&
-                                       ((is_pd && c->length <= 64) || (is_na && c->length == 128))) {
+                       if ((c->clid_len == clid_len && !memcmp(c->clid_data, clid_data, clid_len)) &&
+                           c->iaid == ia->iaid && (INFINITE_VALID(c->valid_until) || now < c->valid_until) &&
+                           ((is_pd && c->length <= 64) || (is_na && c->length == 128))) {
                                a = c;
 
                                /* Reset state */
                                if (a->flags & OAF_BOUND)
                                        apply_lease(iface, a, false);
 
-                               memcpy(a->clid_data, clid_data, clid_len);
-                               a->clid_len = clid_len;
-                               a->iaid = ia->iaid;
-                               a->peer = *addr;
                                stop_reconf(a);
                                break;
                        }
                }
 
+               if (l && a && a->lease != l) {
+                       dhcpv6_ia_free_assignment(a);
+                       a = NULL;
+               }
+
                /* Generic message handling */
                uint16_t status = DHCPV6_STATUS_OK;
                if (a && a->managed_size < 0)
@@ -1278,34 +1245,52 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
                                (hdr->msg_type == DHCPV6_MSG_REBIND && !a)) {
                        bool assigned = !!a;
 
-                       if (!a && !iface->no_dynamic_dhcp && (iface->dhcpv6_pd || iface->dhcpv6_na)) {
-                               /* Create new binding */
-                               a = calloc(1, sizeof(*a) + clid_len);
-                               if (a) {
-                                       a->clid_len = clid_len;
-                                       a->iaid = ia->iaid;
-                                       a->length = reqlen;
-                                       a->peer = *addr;
-                                       a->assigned = reqhint;
-                                       /* Set valid time to current time indicating  */
-                                       /* assignment is not having infinite lifetime */
-                                       a->valid_until = now;
-                                       a->iface = iface;
-
-                                       if (first)
-                                               memcpy(a->key, first->key, sizeof(a->key));
-                                       else
-                                               odhcpd_urandom(a->key, sizeof(a->key));
-                                       memcpy(a->clid_data, clid_data, clid_len);
-
-                                       if (is_pd && iface->dhcpv6_pd)
-                                               while (!(assigned = assign_pd(iface, a)) &&
-                                                               !a->managed_size && ++a->length <= 64);
-                                       else if (is_na && iface->dhcpv6_na)
-                                               assigned = assign_na(iface, a);
-
-                                       if (a->managed_size && !assigned)
-                                               return -1;
+                       if (!a) {
+                               if ((!iface->no_dynamic_dhcp || (l && is_na)) &&
+                                   (iface->dhcpv6_pd || iface->dhcpv6_na)) {
+                                       /* Create new binding */
+                                       a = calloc(1, sizeof(*a) + clid_len);
+
+                                       if (a) {
+                                               a->clid_len = clid_len;
+                                               memcpy(a->clid_data, clid_data, clid_len);
+                                               a->iaid = ia->iaid;
+                                               a->length = reqlen;
+                                               a->peer = *addr;
+                                               a->assigned = is_na && l ? l->hostid : reqhint;
+                                               /* Set valid time to 0 for static lease indicating */
+                                               /* infinite lifetime otherwise current time        */
+                                               a->valid_until = l ? 0 : now;
+                                               a->iface = iface;
+                                               a->flags = OAF_DHCPV6;
+
+                                               if (first)
+                                                       memcpy(a->key, first->key, sizeof(a->key));
+                                               else
+                                                       odhcpd_urandom(a->key, sizeof(a->key));
+
+                                               if (is_pd && iface->dhcpv6_pd)
+                                                       while (!(assigned = assign_pd(iface, a)) &&
+                                                              !a->managed_size && ++a->length <= 64);
+                                               else if (is_na && iface->dhcpv6_na)
+                                                       assigned = assign_na(iface, a);
+
+                                               if (l && assigned) {
+                                                       a->flags |= OAF_STATIC;
+
+                                                       if (l->hostname)
+                                                               a->hostname = strdup(l->hostname);
+
+                                                       if (l->leasetime)
+                                                               a->leasetime = l->leasetime;
+
+                                                       list_add(&a->lease_list, &l->assignments);
+                                                       a->lease = l;
+                                               }
+
+                                               if (a->managed_size && !assigned)
+                                                       return -1;
+                                       }
                                }
                        }
 
@@ -1362,7 +1347,7 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
                                   ((hdr->msg_type == DHCPV6_MSG_SOLICIT && rapid_commit) ||
                                    hdr->msg_type == DHCPV6_MSG_REQUEST ||
                                    hdr->msg_type == DHCPV6_MSG_REBIND)) {
-                               if (hostname_len > 0) {
+                               if ((!(a->flags & OAF_STATIC) || !a->hostname) && hostname_len > 0) {
                                        a->hostname = realloc(a->hostname, hostname_len + 1);
                                        if (a->hostname) {
                                                memcpy(a->hostname, hostname, hostname_len);
@@ -1380,7 +1365,7 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
                                apply_lease(iface, a, true);
                        } else if (!assigned && a && a->managed_size == 0) {
                                /* Cleanup failed assignment */
-                               free_dhcpv6_assignment(a);
+                               dhcpv6_ia_free_assignment(a);
                                a = NULL;
                        }
                } else if (hdr->msg_type == DHCPV6_MSG_RENEW ||
index 8165f7629dd10287c95de7e5f5077b5a93e1f398..6524e84a694e02bcd4ae81d81f3ac831b54b9e86 100644 (file)
@@ -24,6 +24,7 @@
 #include <libubox/uloop.h>
 #include <libubox/avl.h>
 #include <libubox/ustream.h>
+#include <libubox/vlist.h>
 
 // RFC 6106 defines this router advertisement option
 #define ND_OPT_ROUTE_INFO 24
@@ -42,7 +43,7 @@
 
 struct interface;
 struct nl_sock;
-extern struct list_head leases;
+extern struct vlist_tree leases;
 
 struct odhcpd_event {
        struct uloop_fd uloop;
@@ -127,6 +128,8 @@ enum odhcpd_assignment_flags {
        OAF_BOUND               = (1 << 1),
        OAF_STATIC              = (1 << 2),
        OAF_BROKEN_HOSTNAME     = (1 << 3),
+       OAF_DHCPV4              = (1 << 4),
+       OAF_DHCPV6              = (1 << 5),
 };
 
 struct config {
@@ -139,14 +142,15 @@ struct config {
 
 
 struct lease {
-       struct list_head head;
-       struct in_addr ipaddr;
+       struct vlist_node node;
+       struct list_head assignments;
+       uint32_t ipaddr;
        uint32_t hostid;
        struct ether_addr mac;
        uint16_t duid_len;
        uint8_t *duid;
-       uint32_t dhcpv4_leasetime;
-       char hostname[];
+       uint32_t leasetime;
+       char *hostname;
 };
 
 
@@ -154,7 +158,10 @@ struct odhcpd_ref_ip;
 
 struct dhcp_assignment {
        struct list_head head;
+       struct list_head lease_list;
+
        struct interface *iface;
+       struct lease *lease;
 
        struct sockaddr_in6 peer;
        time_t valid_until;
@@ -183,7 +190,7 @@ struct dhcp_assignment {
 #define hwaddr         mac
        uint8_t mac[6];
 
-       uint8_t clid_len;
+       uint16_t clid_len;
        uint8_t clid_data[];
 };
 
@@ -326,6 +333,10 @@ bool odhcpd_bitlen2netmask(bool v6, unsigned int bits, void *mask);
 bool odhcpd_valid_hostname(const char *name);
 
 int config_parse_interface(void *data, size_t len, const char *iname, bool overwrite);
+struct lease *config_find_lease_by_duid(const uint8_t *duid, const uint16_t len);
+struct lease *config_find_lease_by_mac(const uint8_t *mac);
+struct lease *config_find_lease_by_hostid(const uint32_t hostid);
+struct lease *config_find_lease_by_ipaddr(const uint32_t ipaddr);
 
 #ifdef WITH_UBUS
 int ubus_init(void);
@@ -336,6 +347,8 @@ void ubus_bcast_dhcp_event(const char *type, const uint8_t *mac, const size_t ma
                const struct in_addr *addr, const char *name, const char *interface);
 #endif
 
+void dhcpv4_free_assignment(struct dhcp_assignment *a);
+
 ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *iface,
                const struct sockaddr_in6 *addr, const void *data, const uint8_t *end);
 int dhcpv6_ia_init(void);
@@ -343,6 +356,7 @@ int dhcpv6_ia_setup_interface(struct interface *iface, bool enable);
 void dhcpv6_ia_enum_addrs(struct interface *iface, struct dhcp_assignment *c, time_t now,
                                dhcpv6_binding_cb_handler_t func, void *arg);
 void dhcpv6_ia_write_statefile(void);
+void dhcpv6_ia_free_assignment(struct dhcp_assignment *c);
 
 int netlink_add_netevent_handler(struct netevent_handler *hdlr);
 ssize_t netlink_get_interface_addrs(const int ifindex, bool v6,