#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
+#include <poll.h>
#include <alloca.h>
#include <resolv.h>
#include <limits.h>
for (size_t i = 0; i < addrlen; ++i) {
if (addrs[i].prefix > 96)
continue;
+ if (c->valid_until <= now)
+ continue;
addr = addrs[i].addr;
if (c->length == 128)
inet_ntop(AF_INET6, &addr, ipbuf, sizeof(ipbuf) - 1);
- if (c->length == 128 && c->hostname && i == 0) {
+ if (c->length == 128 && c->hostname) {
fputs(ipbuf, fp);
char b[256];
md5_hash(c->hostname, strlen(c->hostname), &md5);
}
- l += snprintf(leasebuf + l, sizeof(leasebuf) - l, "%s/%hhu ", ipbuf,
+ l += snprintf(leasebuf + l, sizeof(leasebuf) - l, "%s/%d ", ipbuf,
(c->managed_size) ? addrs[i].prefix : c->length);
}
leasebuf[l - 1] = '\n';
if (first && c->managed_size == 0)
free_dhcpv6_assignment(c);
+ else if (first)
+ c->valid_until = now + 150;
}
assign->managed_size = -1;
assign->valid_until = odhcpd_time() + 15;
list_add(&assign->head, &iface->ia_assignments);
+
+ // Wait initial period of up to 250ms for immediate assignment
+ struct pollfd pfd = { .fd = fd, .events = POLLIN };
+ poll(&pfd, 1, 250);
+ managed_handle_pd_data(&assign->managed_sock.stream, 0);
+
+ if (fcntl(fd, F_GETFL) >= 0 && assign->managed_size > 0)
+ return true;
}
return false;
int minprefix = -1;
for (int i = 0; i < len; ++i) {
- if (addr[i].prefix > minprefix)
+ if (addr[i].preferred > 0 && addr[i].prefix > minprefix)
minprefix = addr[i].prefix;
addr[i].addr.s6_addr32[3] = 0;
if (change) {
struct dhcpv6_assignment *c;
list_for_each_entry(c, &iface->ia_assignments, head)
- if (c != border)
+ if (c != border && !iface->managed)
apply_lease(iface, c, false);
}
datalen += sizeof(stat);
} else {
if (a) {
- uint32_t pref = 3600;
- uint32_t valid = 3600;
+ uint32_t leasetime = iface->dhcpv4_leasetime;
+ if (leasetime == 0)
+ leasetime = 3600;
+ else if (leasetime < 60)
+ leasetime = 60;
+
+ uint32_t pref = leasetime;
+ uint32_t valid = leasetime;
struct odhcpd_ipaddr *addrs = (a->managed) ? a->managed : iface->ia_addr;
size_t addrlen = (a->managed) ? (size_t)a->managed_size : iface->ia_addr_len;
addr.s6_addr32[1] |= htonl(a->assigned);
if (!memcmp(&p->addr, &addr, sizeof(addr)) &&
- p->prefix == a->length)
+ p->prefix == ((a->managed) ? addrs[i].prefix : a->length))
found = true;
} else {
addr.s6_addr32[3] = htonl(a->assigned);
}
+static void dhcpv6_log(uint8_t msgtype, struct interface *iface, time_t now,
+ const char *duidbuf, bool is_pd, struct dhcpv6_assignment *a, int code)
+{
+ const char *type = "UNKNOWN";
+ const char *status = "UNKNOWN";
+
+ if (msgtype == DHCPV6_MSG_RENEW)
+ return;
+
+ switch (msgtype) {
+ case DHCPV6_MSG_SOLICIT:
+ type = "SOLICIT";
+ break;
+ case DHCPV6_MSG_REQUEST:
+ type = "REQUEST";
+ break;
+ case DHCPV6_MSG_CONFIRM:
+ type = "CONFIRM";
+ break;
+ case DHCPV6_MSG_RENEW:
+ type = "RENEW";
+ break;
+ case DHCPV6_MSG_REBIND:
+ type = "REBIND";
+ break;
+ case DHCPV6_MSG_RELEASE:
+ type = "RELEASE";
+ break;
+ case DHCPV6_MSG_DECLINE:
+ type = "DECLINE";
+ break;
+ }
+
+ switch (code) {
+ case DHCPV6_STATUS_OK:
+ status = "ok";
+ break;
+ case DHCPV6_STATUS_NOADDRSAVAIL:
+ status = "no addresses available";
+ break;
+ case DHCPV6_STATUS_NOBINDING:
+ status = "no binding";
+ break;
+ case DHCPV6_STATUS_NOTONLINK:
+ status = "not on-link";
+ break;
+ case DHCPV6_STATUS_NOPREFIXAVAIL:
+ status = "no prefix available";
+ break;
+ }
+
+ struct odhcpd_ipaddr *addrs = (a->managed) ? a->managed : iface->ia_addr;
+ size_t addrlen = (a->managed) ? (size_t)a->managed_size : iface->ia_addr_len;
+ char leasebuf[256] = "";
+ size_t lbsize = 0;
+ char addrbuf[INET6_ADDRSTRLEN];
+
+ for (size_t i = 0; i < addrlen; ++i) {
+ if (addrs[i].prefix > 96 || addrs[i].preferred <= now)
+ continue;
+
+ struct in6_addr addr = addrs[i].addr;
+ int prefix = a->managed ? addrs[i].prefix : a->length;
+ if (prefix == 128)
+ addr.s6_addr32[3] = htonl(a->assigned);
+ else
+ addr.s6_addr32[1] |= htonl(a->assigned);
+
+ inet_ntop(AF_INET6, &addr, addrbuf, sizeof(addrbuf));
+ lbsize += snprintf(leasebuf + lbsize, sizeof(leasebuf) - lbsize, "%s/%d ", addrbuf, prefix);
+ }
+
+ syslog(LOG_WARNING, "DHCPV6 %s %s from %s on %s: %s %s", type, (is_pd) ? "IA_PD" : "IA_NA",
+ duidbuf, iface->ifname, status, leasebuf);
+}
+
+
+
ssize_t dhcpv6_handle_ia(uint8_t *buf, size_t buflen, struct interface *iface,
const struct sockaddr_in6 *addr, const void *data, const uint8_t *end)
{
char hostname[256];
size_t hostname_len = 0;
bool class_oro = false;
+ bool notonlink = false;
+ char duidbuf[261];
+
dhcpv6_for_each_option(start, end, otype, olen, odata) {
if (otype == DHCPV6_OPT_CLIENTID) {
clid_data = odata;
memcpy(mac, &odata[8], sizeof(mac));
else if (olen == 10 && odata[0] == 0 && odata[1] == 3)
memcpy(mac, &odata[4], sizeof(mac));
+
+ if (olen <= 130)
+ odhcpd_hexlify(duidbuf, odata, olen);
} else if (otype == DHCPV6_OPT_FQDN && olen >= 2 && olen <= 255) {
uint8_t fqdn_buf[256];
memcpy(fqdn_buf, odata, olen);
goto out;
update(iface);
- bool update_state = false;
struct dhcpv6_assignment *first = NULL;
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);
+ bool ia_addr_present = false;
if (!is_pd && !is_na)
continue;
if (stype != DHCPV6_OPT_IA_ADDR || slen < sizeof(struct dhcpv6_ia_addr) - 4)
continue;
+ ia_addr_present = true;
#ifdef DHCPV6_OPT_PREFIX_CLASS
uint8_t *xdata;
uint16_t xtype, xlen;
}
a->accept_reconf = accept_reconf;
apply_lease(iface, a, true);
- update_state = true;
} else if (!assigned && a && a->managed_size == 0) { // Cleanup failed assignment
free_dhcpv6_assignment(a);
}
} else if (hdr->msg_type == DHCPV6_MSG_RELEASE) {
a->valid_until = 0;
apply_lease(iface, a, false);
- update_state = true;
} else if (hdr->msg_type == DHCPV6_MSG_DECLINE && a->length == 128) {
a->clid_len = 0;
a->valid_until = now + 3600; // Block address for 1h
- update_state = true;
}
- } else if (hdr->msg_type == DHCPV6_MSG_CONFIRM) {
- // Always send NOTONLINK for CONFIRM so that clients restart connection
+ } else if (hdr->msg_type == DHCPV6_MSG_CONFIRM && ia_addr_present) {
+ // Send NOTONLINK for CONFIRM with addr present so that clients restart connection
status = DHCPV6_STATUS_NOTONLINK;
ia_response_len = append_reply(buf, buflen, status, ia, a, iface, true);
+ notonlink = true;
}
buf += ia_response_len;
buflen -= ia_response_len;
response_len += ia_response_len;
+ dhcpv6_log(hdr->msg_type, iface, now, duidbuf, is_pd, a, status);
}
- if (hdr->msg_type == DHCPV6_MSG_RELEASE && response_len + 6 < buflen) {
+ if ((hdr->msg_type == DHCPV6_MSG_RELEASE || hdr->msg_type == DHCPV6_MSG_DECLINE || notonlink) &&
+ response_len + 6 < buflen) {
buf[0] = 0;
buf[1] = DHCPV6_OPT_STATUS;
buf[2] = 0;
buf[3] = 2;
buf[4] = 0;
- buf[5] = DHCPV6_STATUS_OK;
+ buf[5] = (notonlink) ? DHCPV6_STATUS_NOTONLINK : DHCPV6_STATUS_OK;
response_len += 6;
}
- if (update_state)
- dhcpv6_write_statefile();
+ dhcpv6_write_statefile();
out:
return response_len;