dhcpv6-ia: fix prefix delegation behavior
[project/odhcpd.git] / src / dhcpv6-ia.c
index 81c2481aef40787f5bfaf32c7438bbe44ef62fd3..2de971070df10008965a190d693fae223a133eb8 100644 (file)
@@ -40,6 +40,7 @@
      (addrs)[(i)].prefix > 64)
 
 static void dhcpv6_netevent_cb(unsigned long event, struct netevent_handler_info *info);
+static void apply_lease(struct dhcp_assignment *a, bool add);
 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);
@@ -62,23 +63,16 @@ int dhcpv6_ia_init(void)
 
 int dhcpv6_ia_setup_interface(struct interface *iface, bool enable)
 {
-       if (!enable) {
-               struct dhcp_assignment *c;
-
-               while (!list_empty(&iface->ia_assignments)) {
-                       c = list_first_entry(&iface->ia_assignments, struct dhcp_assignment, head);
-                       free_assignment(c);
-               }
-       }
+       enable = enable && (iface->dhcpv6 == MODE_SERVER);
 
-       if (enable && iface->dhcpv6 == MODE_SERVER) {
+       if (enable) {
                struct dhcp_assignment *border;
 
                if (list_empty(&iface->ia_assignments)) {
-                       border = calloc(1, sizeof(*border));
+                       border = alloc_assignment(0);
 
                        if (!border) {
-                               syslog(LOG_ERR, "Calloc failed for border on %s", iface->name);
+                               syslog(LOG_WARNING, "Failed to alloc border on %s", iface->name);
                                return -1;
                        }
 
@@ -88,7 +82,15 @@ 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);
+       } else {
+               struct dhcp_assignment *c;
+
+               while (!list_empty(&iface->ia_assignments)) {
+                       c = list_first_entry(&iface->ia_assignments, struct dhcp_assignment, head);
+                       free_assignment(c);
+               }
        }
+
        return 0;
 }
 
@@ -205,6 +207,9 @@ static void dhcpv6_ia_free_assignment(struct dhcp_assignment *a)
                close(a->managed_sock.fd.fd);
        }
 
+       if ((a->flags & OAF_BOUND) && (a->flags & OAF_DHCPV6_PD))
+               apply_lease(a, false);
+
        if (a->reconf_cnt)
                stop_reconf(a);
 
@@ -229,7 +234,7 @@ void dhcpv6_ia_enum_addrs(struct interface *iface, struct dhcp_assignment *c,
                addr = addrs[i].addr.in6;
                pref = addrs[i].preferred;
                valid = addrs[i].valid;
-               if (prefix == 128) {
+               if (c->flags & OAF_DHCPV6_NA) {
                        if (!ADDR_ENTRY_VALID_IA_ADDR(iface, i, m, addrs))
                                continue;
 
@@ -242,9 +247,15 @@ void dhcpv6_ia_enum_addrs(struct interface *iface, struct dhcp_assignment *c,
                        addr.s6_addr32[2] = addr.s6_addr32[3] = 0;
                }
 
+               if (pref > (uint32_t)c->valid_until)
+                       pref = c->valid_until;
+
                if (pref != UINT32_MAX)
                        pref -= now;
 
+               if (valid > (uint32_t)c->valid_until)
+                       valid = c->valid_until;
+
                if (valid != UINT32_MAX)
                        valid -= now;
 
@@ -270,7 +281,7 @@ static void dhcpv6_write_ia_addr(struct in6_addr *addr, int prefix, _unused uint
 
        inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf) - 1);
 
-       if (ctxt->c->length == 128 && ctxt->c->hostname &&
+       if ((ctxt->c->flags & OAF_DHCPV6_NA) && ctxt->c->hostname &&
            !(ctxt->c->flags & OAF_BROKEN_HOSTNAME)) {
                fputs(ipbuf, ctxt->fp);
 
@@ -295,18 +306,24 @@ void dhcpv6_ia_write_statefile(void)
        md5_begin(&ctxt.md5);
 
        if (config.dhcp_statefile) {
+               unsigned tmp_statefile_strlen = strlen(config.dhcp_statefile) + strlen(".tmp") + 1;
+               char *tmp_statefile = alloca(tmp_statefile_strlen);
                time_t now = odhcpd_time(), wall_time = time(NULL);
-               int fd = open(config.dhcp_statefile, O_CREAT | O_WRONLY | O_CLOEXEC, 0644);
+               int fd, ret;
                char leasebuf[512];
 
+               snprintf(tmp_statefile, tmp_statefile_strlen, "%s.tmp", config.dhcp_statefile);
+
+               fd = open(tmp_statefile, O_CREAT | O_WRONLY | O_CLOEXEC, 0644);
                if (fd < 0)
                        return;
-               int ret;
+
                ret = lockf(fd, F_LOCK, 0);
                if (ret < 0) {
                        close(fd);
                        return;
                }
+
                if (ftruncate(fd, 0) < 0) {}
 
                ctxt.fp = fdopen(fd, "w");
@@ -333,12 +350,12 @@ void dhcpv6_ia_write_statefile(void)
                                        odhcpd_hexlify(duidbuf, ctxt.c->clid_data, ctxt.c->clid_len);
 
                                        /* iface DUID iaid hostname lifetime assigned length [addrs...] */
-                                       ctxt.buf_idx = snprintf(ctxt.buf, ctxt.buf_len, "# %s %s %x %s%s %ld %x %u ",
+                                       ctxt.buf_idx = snprintf(ctxt.buf, ctxt.buf_len, "# %s %s %x %s%s %"PRId64" %x %u ",
                                                                ctxt.iface->ifname, duidbuf, ntohl(ctxt.c->iaid),
                                                                (ctxt.c->flags & OAF_BROKEN_HOSTNAME) ? "broken\\x20" : "",
                                                                (ctxt.c->hostname ? ctxt.c->hostname : "-"),
                                                                (ctxt.c->valid_until > now ?
-                                                                       (ctxt.c->valid_until - now + wall_time) :
+                                                                       (int64_t)(ctxt.c->valid_until - now + wall_time) :
                                                                        (INFINITE_VALID(ctxt.c->valid_until) ? -1 : 0)),
                                                                ctxt.c->assigned, (unsigned)ctxt.c->length);
 
@@ -363,12 +380,12 @@ void dhcpv6_ia_write_statefile(void)
                                        odhcpd_hexlify(duidbuf, c->hwaddr, sizeof(c->hwaddr));
 
                                        /* iface DUID iaid hostname lifetime assigned length [addrs...] */
-                                       ctxt.buf_idx = snprintf(ctxt.buf, ctxt.buf_len, "# %s %s ipv4 %s%s %ld %x 32 ",
+                                       ctxt.buf_idx = snprintf(ctxt.buf, ctxt.buf_len, "# %s %s ipv4 %s%s %"PRId64" %x 32 ",
                                                                ctxt.iface->ifname, duidbuf,
                                                                (c->flags & OAF_BROKEN_HOSTNAME) ? "broken\\x20" : "",
                                                                (c->hostname ? c->hostname : "-"),
                                                                (c->valid_until > now ?
-                                                                       (c->valid_until - now + wall_time) :
+                                                                       (int64_t)(c->valid_until - now + wall_time) :
                                                                        (INFINITE_VALID(c->valid_until) ? -1 : 0)),
                                                                ntohl(c->addr));
 
@@ -399,6 +416,8 @@ void dhcpv6_ia_write_statefile(void)
                }
 
                fclose(ctxt.fp);
+
+               rename(tmp_statefile, config.dhcp_statefile);
        }
 
        uint8_t newmd5[16];
@@ -414,10 +433,10 @@ void dhcpv6_ia_write_statefile(void)
        }
 }
 
-static void __apply_lease(struct interface *iface, struct dhcp_assignment *a,
+static void __apply_lease(struct dhcp_assignment *a,
                struct odhcpd_ipaddr *addrs, ssize_t addr_len, bool add)
 {
-       if (a->length > 64)
+       if (a->flags & OAF_DHCPV6_NA)
                return;
 
        for (ssize_t i = 0; i < addr_len; ++i) {
@@ -425,16 +444,17 @@ static void __apply_lease(struct interface *iface, struct dhcp_assignment *a,
                prefix.s6_addr32[1] |= htonl(a->assigned);
                prefix.s6_addr32[2] = prefix.s6_addr32[3] = 0;
                netlink_setup_route(&prefix, (a->managed_size) ? addrs[i].prefix : a->length,
-                               iface->ifindex, &a->peer.sin6_addr, 1024, add);
+                               a->iface->ifindex, &a->peer.sin6_addr, 1024, add);
        }
 }
 
-static void apply_lease(struct interface *iface, struct dhcp_assignment *a, bool add)
+static void apply_lease(struct dhcp_assignment *a, bool add)
 {
+       struct interface *iface = a->iface;
        struct odhcpd_ipaddr *addrs = (a->managed) ? a->managed : iface->addr6;
        ssize_t addrlen = (a->managed) ? a->managed_size : (ssize_t)iface->addr6_len;
 
-       __apply_lease(iface, a, addrs, addrlen, add);
+       __apply_lease(a, addrs, addrlen, add);
 }
 
 /* Set border assignment size based on the IPv6 address prefixes */
@@ -523,7 +543,7 @@ static void managed_handle_pd_data(struct ustream *s, _unused int bytes_new)
 
        if (first && c->managed_size == 0)
                free_assignment(c);
-       else if (first && !(c->flags & OAF_STATIC))
+       else if (first)
                c->valid_until = now + 150;
 }
 
@@ -534,8 +554,7 @@ static void managed_handle_pd_done(struct ustream *s)
        struct ustream_fd *fd = container_of(s, struct ustream_fd, stream);
        struct dhcp_assignment *c = container_of(fd, struct dhcp_assignment, managed_sock);
 
-       if (!(c->flags & OAF_STATIC))
-               c->valid_until = odhcpd_time() + 15;
+       c->valid_until = odhcpd_time() + 15;
 
        c->managed_size = 0;
 
@@ -563,8 +582,7 @@ static bool assign_pd(struct interface *iface, struct dhcp_assignment *assign)
                        ustream_write_pending(&assign->managed_sock.stream);
                        assign->managed_size = -1;
 
-                       if (!(assign->flags & OAF_STATIC))
-                               assign->valid_until = odhcpd_time() + 15;
+                       assign->valid_until = odhcpd_time() + 15;
 
                        list_add(&assign->head, &iface->ia_assignments);
 
@@ -588,14 +606,14 @@ static bool assign_pd(struct interface *iface, struct dhcp_assignment *assign)
        uint32_t current = 1, asize = (1 << (64 - assign->length)) - 1;
        if (assign->assigned) {
                list_for_each_entry(c, &iface->ia_assignments, head) {
-                       if (c->length == 128 || c->length == 0)
+                       if (c->flags & OAF_DHCPV6_NA)
                                continue;
 
                        if (assign->assigned >= current && assign->assigned + asize < c->assigned) {
                                list_add_tail(&assign->head, &c->head);
 
                                if (assign->flags & OAF_BOUND)
-                                       apply_lease(iface, assign, true);
+                                       apply_lease(assign, true);
 
                                return true;
                        }
@@ -608,7 +626,7 @@ static bool assign_pd(struct interface *iface, struct dhcp_assignment *assign)
        /* Fallback to a variable assignment */
        current = 1;
        list_for_each_entry(c, &iface->ia_assignments, head) {
-               if (c->length == 128 || c->length == 0)
+               if (c->flags & OAF_DHCPV6_NA)
                        continue;
 
                current = (current + asize) & (~asize);
@@ -617,7 +635,7 @@ static bool assign_pd(struct interface *iface, struct dhcp_assignment *assign)
                        list_add_tail(&assign->head, &c->head);
 
                        if (assign->flags & OAF_BOUND)
-                               apply_lease(iface, assign, true);
+                               apply_lease(assign, true);
 
                        return true;
                }
@@ -637,10 +655,7 @@ static bool assign_na(struct interface *iface, struct dhcp_assignment *a)
        /* 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) {
+                       if (c->assigned > a->assigned || !(c->flags & OAF_DHCPV6_NA)) {
                                list_add_tail(&a->head, &c->head);
                                return true;
                        } else if (c->assigned == a->assigned)
@@ -662,10 +677,7 @@ static bool assign_na(struct interface *iface, struct dhcp_assignment *a)
                        continue;
 
                list_for_each_entry(c, &iface->ia_assignments, head) {
-                       if (c->length == 0)
-                               continue;
-
-                       if (c->assigned > try || c->length != 128) {
+                       if (c->assigned > try || !(c->flags & OAF_DHCPV6_NA)) {
                                a->assigned = try;
                                list_add_tail(&a->head, &c->head);
                                return true;
@@ -686,23 +698,25 @@ static void handle_addrlist_change(struct netevent_handler_info *info)
        time_t now = odhcpd_time();
 
        list_for_each_entry(c, &iface->ia_assignments, head) {
-               if (c != border && iface->ra_managed == RA_MANAGED_NO_MFLAG
+               if ((c->flags & OAF_DHCPV6_PD) && !(iface->ra_flags & ND_RA_FLAG_MANAGED)
                                && (c->flags & OAF_BOUND))
-                       __apply_lease(iface, c, info->addrs_old.addrs,
+                       __apply_lease(c, info->addrs_old.addrs,
                                        info->addrs_old.len, false);
        }
 
        set_border_assignment_size(iface, border);
 
        list_for_each_entry_safe(c, d, &iface->ia_assignments, head) {
-               if (c->clid_len == 0 || (!INFINITE_VALID(c->valid_until) && c->valid_until < now) ||
-                               c->managed_size)
+               if (c->clid_len == 0 ||
+                   !(c->flags & OAF_DHCPV6_PD) ||
+                   (!INFINITE_VALID(c->valid_until) && c->valid_until < now) ||
+                   c->managed_size)
                        continue;
 
-               if (c->length < 128 && c->assigned >= border->assigned && c != border)
+               if (c->assigned == 0 || c->assigned >= border->assigned)
                        list_move(&c->head, &reassign);
-               else if (c != border && (c->flags & OAF_BOUND))
-                       apply_lease(iface, c, true);
+               else if (c->flags & OAF_BOUND)
+                       apply_lease(c, true);
 
                if (c->accept_reconf && c->reconf_cnt == 0) {
                        struct dhcp_assignment *a;
@@ -719,11 +733,9 @@ static void handle_addrlist_change(struct netevent_handler_info *info)
 
        while (!list_empty(&reassign)) {
                c = list_first_entry(&reassign, struct dhcp_assignment, head);
-               list_del(&c->head);
-               if (!assign_pd(iface, c)) {
-                       c->assigned = 0;
-                       list_add(&c->head, &iface->ia_assignments);
-               }
+               list_del_init(&c->head);
+               if (!assign_pd(iface, c))
+                       free_assignment(c);
        }
 
        dhcpv6_ia_write_statefile();
@@ -771,12 +783,8 @@ static void valid_until_cb(struct uloop_timeout *event)
                        continue;
 
                list_for_each_entry_safe(a, n, &iface->ia_assignments, head) {
-                       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_assignment(a);
-
-                       }
+                       if (a->clid_len > 0 && !INFINITE_VALID(a->valid_until) && a->valid_until < now)
+                               free_assignment(a);
                }
        }
        uloop_timeout_set(event, 1000);
@@ -825,7 +833,7 @@ static size_t build_ia(uint8_t *buf, size_t buflen, uint16_t status,
                if (a->leasetime)
                        leasetime = a->leasetime;
                else
-                       leasetime = iface->dhcpv4_leasetime;
+                       leasetime = iface->dhcp_leasetime;
 
                uint32_t pref = leasetime;
                uint32_t valid = leasetime;
@@ -844,10 +852,16 @@ static size_t build_ia(uint8_t *buf, size_t buflen, uint16_t status,
                        if (prefix_pref != UINT32_MAX)
                                prefix_pref -= now;
 
+                       if (prefix_pref > leasetime)
+                               prefix_pref = leasetime;
+
                        if (prefix_valid != UINT32_MAX)
                                prefix_valid -= now;
 
-                       if (a->length < 128) {
+                       if (prefix_valid > leasetime)
+                               prefix_valid = leasetime;
+
+                       if (a->flags & OAF_DHCPV6_PD) {
                                struct dhcpv6_ia_prefix o_ia_p = {
                                        .type = htons(DHCPV6_OPT_IA_PREFIX),
                                        .len = htons(sizeof(o_ia_p) - 4),
@@ -869,7 +883,9 @@ static size_t build_ia(uint8_t *buf, size_t buflen, uint16_t status,
 
                                memcpy(buf + ia_len, &o_ia_p, sizeof(o_ia_p));
                                ia_len += sizeof(o_ia_p);
-                       } else {
+                       }
+
+                       if (a->flags & OAF_DHCPV6_NA) {
                                struct dhcpv6_ia_addr o_ia_a = {
                                        .type = htons(DHCPV6_OPT_IA_ADDR),
                                        .len = htons(sizeof(o_ia_a) - 4),
@@ -1068,8 +1084,8 @@ static void dhcpv6_log(uint8_t msgtype, struct interface *iface, time_t now,
                dhcpv6_ia_enum_addrs(iface, a, now, dhcpv6_log_ia_addr, &ctxt);
        }
 
-       syslog(LOG_NOTICE, "DHCPV6 %s %s from %s on %s: %s %s", type, (is_pd) ? "IA_PD" : "IA_NA",
-                       duidbuf, iface->name, status, leasebuf);
+       syslog(LOG_INFO, "DHCPV6 %s %s from %s on %s: %s %s", type, (is_pd) ? "IA_PD" : "IA_NA",
+                        duidbuf, iface->name, status, leasebuf);
 }
 
 static bool dhcpv6_ia_on_link(const struct dhcpv6_ia_hdr *ia, struct dhcp_assignment *a,
@@ -1206,12 +1222,12 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
                list_for_each_entry(c, &iface->ia_assignments, head) {
                        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))) {
+                           ((is_pd && (c->flags & OAF_DHCPV6_PD)) || (is_na && (c->flags & OAF_DHCPV6_NA)))) {
                                a = c;
 
                                /* Reset state */
                                if (a->flags & OAF_BOUND)
-                                       apply_lease(iface, a, false);
+                                       apply_lease(a, false);
 
                                stop_reconf(a);
                                break;
@@ -1237,7 +1253,7 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
                                if ((!iface->no_dynamic_dhcp || (l && is_na)) &&
                                    (iface->dhcpv6_pd || iface->dhcpv6_na)) {
                                        /* Create new binding */
-                                       a = calloc(1, sizeof(*a) + clid_len);
+                                       a = alloc_assignment(clid_len);
 
                                        if (a) {
                                                a->clid_len = clid_len;
@@ -1246,12 +1262,10 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
                                                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->valid_until =  now;
                                                a->dhcp_free_cb = dhcpv6_ia_free_assignment;
                                                a->iface = iface;
-                                               a->flags = OAF_DHCPV6;
+                                               a->flags = (is_pd ? OAF_DHCPV6_PD : OAF_DHCPV6_NA);
 
                                                if (first)
                                                        memcpy(a->key, first->key, sizeof(a->key));
@@ -1328,9 +1342,8 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
                                a->flags &= ~OAF_BOUND;
                                a->flags |= OAF_TENTATIVE;
 
-                               if (!(a->flags & OAF_STATIC))
-                                       /* Keep tentative assignment around for 60 seconds */
-                                       a->valid_until = now + 60;
+                               /* Keep tentative assignment around for 60 seconds */
+                               a->valid_until = now + 60;
 
                        } else if (assigned &&
                                   ((hdr->msg_type == DHCPV6_MSG_SOLICIT && rapid_commit) ||
@@ -1351,7 +1364,7 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
                                a->accept_reconf = accept_reconf;
                                a->flags &= ~OAF_TENTATIVE;
                                a->flags |= OAF_BOUND;
-                               apply_lease(iface, a, true);
+                               apply_lease(a, true);
                        } else if (!assigned && a && a->managed_size == 0) {
                                /* Cleanup failed assignment */
                                free_assignment(a);
@@ -1369,23 +1382,18 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
                                ia_response_len = build_ia(buf, buflen, status, ia, a, iface, false);
                                if (a) {
                                        a->flags |= OAF_BOUND;
-                                       apply_lease(iface, a, true);
+                                       apply_lease(a, true);
                                }
                        } else if (hdr->msg_type == DHCPV6_MSG_RELEASE) {
-                               if (!(a->flags & OAF_STATIC))
-                                       a->valid_until = now - 1;
-
-                               if (a->flags & OAF_BOUND) {
-                                       apply_lease(iface, a, false);
-                                       a->flags &= ~OAF_BOUND;
-                               }
-                       } else if (hdr->msg_type == DHCPV6_MSG_DECLINE && a->length == 128) {
+                               a->valid_until = now - 1;
+                       } else if ((a->flags & OAF_DHCPV6_NA) && hdr->msg_type == DHCPV6_MSG_DECLINE) {
                                a->flags &= ~OAF_BOUND;
 
-                               if (!(a->flags & OAF_STATIC)) {
-                                       a->clid_len = 0;
+                               if (!(a->flags & OAF_STATIC) || a->lease->hostid != a->assigned) {
+                                       memset(a->clid_data, 0, a->clid_len);
                                        a->valid_until = now + 3600; /* Block address for 1h */
-                               }
+                               } else
+                                       a->valid_until = now - 1;
                        }
                } else if (hdr->msg_type == DHCPV6_MSG_CONFIRM) {
                        if (ia_addr_present && !dhcpv6_ia_on_link(ia, a, iface)) {