From ca8ba91c757b1559bc6391707547d54477c8315a Mon Sep 17 00:00:00 2001 From: Hans Dedecker Date: Tue, 12 Feb 2019 16:07:24 +0100 Subject: dhcp: rework static lease logic 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 --- src/config.c | 246 +++++++++++++++++++++++++++++++++++++++++--------- src/dhcpv4.c | 272 +++++++++++++++++++++++++------------------------------- src/dhcpv6-ia.c | 253 +++++++++++++++++++++++++--------------------------- src/odhcpd.h | 26 ++++-- 4 files changed, 465 insertions(+), 332 deletions(-) diff --git a/src/config.c b/src/config.c index f4a0dcf..d857f24 100644 --- a/src/config.c +++ b/src/config.c @@ -14,12 +14,19 @@ #include #include #include +#include +#include #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(); } - diff --git a/src/dhcpv4.c b/src/dhcpv4.c index 00e4c3f..da2c051 100644 --- a/src/dhcpv4.c +++ b/src/dhcpv4.c @@ -37,11 +37,9 @@ 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; } diff --git a/src/dhcpv6-ia.c b/src/dhcpv6-ia.c index 7c3ccad..4cc6fab 100644 --- a/src/dhcpv6-ia.c +++ b/src/dhcpv6-ia.c @@ -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 || diff --git a/src/odhcpd.h b/src/odhcpd.h index 8165f76..6524e84 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -24,6 +24,7 @@ #include #include #include +#include // 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, -- cgit v1.2.3