router: Add PREF64 (RFC 8781) support
[project/odhcpd.git] / src / router.c
index 541c0237dd7d21e75e3eefc796393ece33739c77..d5ef7f8a4f582ef3466024da3ff2981a41c2787c 100644 (file)
@@ -390,6 +390,7 @@ enum {
        IOV_RA_ROUTES,
        IOV_RA_DNS,
        IOV_RA_SEARCH,
+       IOV_RA_PREF64,
        IOV_RA_ADV_INTERVAL,
        IOV_RA_TOTAL,
 };
@@ -427,6 +428,13 @@ struct nd_opt_route_info {
        uint32_t addr[4];
 };
 
+struct nd_opt_pref64_info {
+       uint8_t type;
+       uint8_t len;
+       uint16_t lifetime_plc;
+       uint32_t addr[3];
+};
+
 /* Router Advert server mode */
 static int send_router_advert(struct interface *iface, const struct in6_addr *from)
 {
@@ -437,11 +445,13 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr
        struct nd_opt_dns_server *dns = NULL;
        struct nd_opt_search_list *search = NULL;
        struct nd_opt_route_info *routes = NULL;
+       struct nd_opt_pref64_info *pref64 = NULL;
        struct nd_opt_adv_interval adv_interval;
        struct iovec iov[IOV_RA_TOTAL];
        struct sockaddr_in6 dest;
-       size_t dns_sz = 0, search_sz = 0, pfxs_cnt = 0, routes_cnt = 0;
-       ssize_t addr_cnt = 0;
+       size_t dns_sz = 0, search_sz = 0, pref64_sz = 0;
+       size_t pfxs_cnt = 0, routes_cnt = 0;
+       ssize_t valid_addr_cnt = 0, invalid_addr_cnt = 0;
        uint32_t minvalid = UINT32_MAX, maxival, lifetime;
        int msecs, mtu = iface->ra_mtu, hlim = iface->ra_hoplimit;
        bool default_route = false;
@@ -485,33 +495,64 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr
        iov[IOV_RA_ADV].iov_base = (char *)&adv;
        iov[IOV_RA_ADV].iov_len = sizeof(adv);
 
-       /* If not shutdown */
-       if (iface->timer_rs.cb) {
-               size_t size = sizeof(*addrs) * iface->addr6_len;
+       valid_addr_cnt = (iface->timer_rs.cb /* if not shutdown */ ? iface->addr6_len : 0);
+       invalid_addr_cnt = iface->invalid_addr6_len;
+
+       // check ra_default
+       if (iface->default_router) {
+               default_route = true;
+
+               if (iface->default_router > 1)
+                       valid_prefix = true;
+       }
+
+       if (valid_addr_cnt + invalid_addr_cnt) {
+               addrs = alloca(sizeof(*addrs) * (valid_addr_cnt + invalid_addr_cnt));
+
+               if (valid_addr_cnt) {
+                       memcpy(addrs, iface->addr6, sizeof(*addrs) * valid_addr_cnt);
 
-               addrs = alloca(size);
-               memcpy(addrs, iface->addr6, size);
+                       /* Check default route */
+                       if (!default_route && parse_routes(addrs, valid_addr_cnt))
+                               default_route = true;
+               }
+
+               if (invalid_addr_cnt) {
+                       size_t i = 0;
+
+                       memcpy(&addrs[valid_addr_cnt], iface->invalid_addr6, sizeof(*addrs) * invalid_addr_cnt);
 
-               addr_cnt = iface->addr6_len;
+                       /* Remove invalid prefixes that were advertised 3 times */
+                       while (i < iface->invalid_addr6_len) {
+                               if (++iface->invalid_addr6[i].invalid_advertisements >= 3) {
+                                       if (i + 1 < iface->invalid_addr6_len)
+                                               memmove(&iface->invalid_addr6[i], &iface->invalid_addr6[i + 1], sizeof(*addrs) * (iface->invalid_addr6_len - i - 1));
 
-               /* Check default route */
-               if (iface->default_router) {
-                       default_route = true;
+                                       iface->invalid_addr6_len--;
 
-                       if (iface->default_router > 1)
-                               valid_prefix = true;
-               } else if (parse_routes(addrs, addr_cnt))
-                       default_route = true;
+                                       if (iface->invalid_addr6_len) {
+                                               struct odhcpd_ipaddr *new_invalid_addr6 = realloc(iface->invalid_addr6, sizeof(*addrs) * iface->invalid_addr6_len);
+
+                                               if (new_invalid_addr6)
+                                                       iface->invalid_addr6 = new_invalid_addr6;
+                                       } else {
+                                               free(iface->invalid_addr6);
+                                               iface->invalid_addr6 = NULL;
+                                       }
+                               } else
+                                       ++i;
+                       }
+               }
        }
 
        /* Construct Prefix Information options */
-       for (ssize_t i = 0; i < addr_cnt; ++i) {
+       for (ssize_t i = 0; i < valid_addr_cnt + invalid_addr_cnt; ++i) {
                struct odhcpd_ipaddr *addr = &addrs[i];
                struct nd_opt_prefix_info *p = NULL;
                uint32_t preferred = 0;
                uint32_t valid = 0;
 
-               if (addr->prefix > 96 || addr->valid <= (uint32_t)now) {
+               if (addr->prefix > 96 || (i < valid_addr_cnt && addr->valid <= (uint32_t)now)) {
                        syslog(LOG_INFO, "Address %s (prefix %d, valid %u) not suitable as RA prefix on %s",
                                inet_ntop(AF_INET6, &addr->addr.in6, buf, sizeof(buf)), addr->prefix,
                                addr->valid, iface->name);
@@ -554,14 +595,17 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr
                                preferred = iface->preferred_lifetime;
                }
 
-               valid = TIME_LEFT(addr->valid, now);
-               if (iface->ra_useleasetime && valid > iface->dhcp_leasetime)
-                       valid = iface->dhcp_leasetime;
+               if (addr->valid > (uint32_t)now) {
+                       valid = TIME_LEFT(addr->valid, now);
+
+                       if (iface->ra_useleasetime && valid > iface->dhcp_leasetime)
+                               valid = iface->dhcp_leasetime;
+               }
 
                if (minvalid > valid)
                        minvalid = valid;
 
-               if (!IN6_IS_ADDR_ULA(&addr->addr.in6) || iface->default_router)
+               if ((!IN6_IS_ADDR_ULA(&addr->addr.in6) || iface->default_router) && valid)
                        valid_prefix = true;
 
                odhcpd_bmemcpy(&p->nd_opt_pi_prefix, &addr->addr.in6,
@@ -587,17 +631,24 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr
        msecs = calc_adv_interval(iface, minvalid, &maxival);
        lifetime = calc_ra_lifetime(iface, maxival);
 
-       if (default_route) {
-               if (!valid_prefix) {
-                       syslog(LOG_WARNING, "A default route is present but there is no public prefix "
-                                       "on %s thus we don't announce a default route!", iface->name);
-                       adv.h.nd_ra_router_lifetime = 0;
-               } else
-                       adv.h.nd_ra_router_lifetime = htons(lifetime < UINT16_MAX ? lifetime : UINT16_MAX);
+       if (!iface->have_link_local) {
+               syslog(LOG_NOTICE, "Skip sending a RA on %s as no link local address is available", iface->name);
+               goto out;
+       }
 
-       } else
+       if (default_route && valid_prefix) {
+               adv.h.nd_ra_router_lifetime = htons(lifetime < UINT16_MAX ? lifetime : UINT16_MAX);
+       } else {
                adv.h.nd_ra_router_lifetime = 0;
 
+               if (default_route) {
+                       syslog(LOG_WARNING, "A default route is present but there is no public prefix "
+                                           "on %s thus we don't announce a default route by overriding ra_lifetime!", iface->name);
+               } else {
+                       syslog(LOG_WARNING, "No default route present, overriding ra_lifetime!");
+               }
+       }
+
        syslog(LOG_DEBUG, "Using a RA lifetime of %d seconds on %s", ntohs(adv.h.nd_ra_router_lifetime), iface->name);
 
        /* DNS options */
@@ -657,6 +708,65 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr
        iov[IOV_RA_SEARCH].iov_base = (char *)search;
        iov[IOV_RA_SEARCH].iov_len = search_sz;
 
+       if (iface->pref64_length) {
+               /* RFC 8781 § 4.1 rounding up lifetime to multiply of 8 */
+               uint16_t pref64_lifetime = lifetime < (UINT16_MAX - 7) ? lifetime + 7 : UINT16_MAX;
+               uint8_t prefix_length_code;
+               uint32_t mask_a1, mask_a2;
+
+               switch (iface->pref64_length) {
+               case 96:
+                       prefix_length_code = 0;
+                       mask_a1 = 0xffffffff;
+                       mask_a2 = 0xffffffff;
+                       break;
+               case 64:
+                       prefix_length_code = 1;
+                       mask_a1 = 0xffffffff;
+                       mask_a2 = 0x00000000;
+                       break;
+               case 56:
+                       prefix_length_code = 2;
+                       mask_a1 = 0xffffff00;
+                       mask_a2 = 0x00000000;
+                       break;
+               case 48:
+                       prefix_length_code = 3;
+                       mask_a1 = 0xffff0000;
+                       mask_a2 = 0x00000000;
+                       break;
+               case 40:
+                       prefix_length_code = 4;
+                       mask_a1 = 0xff000000;
+                       mask_a2 = 0x00000000;
+                       break;
+               case 32:
+                       prefix_length_code = 5;
+                       mask_a1 = 0x00000000;
+                       mask_a2 = 0x00000000;
+                       break;
+               default:
+                       syslog(LOG_WARNING, "Invalid PREF64 prefix size (%d), "
+                                       "ignoring ra_pref64 option!", iface->pref64_length);
+                       goto pref64_out;
+                       break;
+               }
+
+               pref64_sz = sizeof(*pref64);
+               pref64 = alloca(pref64_sz);
+               memset(pref64, 0, pref64_sz);
+               pref64->type = ND_OPT_PREF64;
+               pref64->len = 2;
+               pref64->lifetime_plc = htons((0xfff8 & pref64_lifetime) |
+                                               (0x7 & prefix_length_code));
+               pref64->addr[0] = iface->pref64_addr.s6_addr32[0];
+               pref64->addr[1] = iface->pref64_addr.s6_addr32[1] & htonl(mask_a1);
+               pref64->addr[2] = iface->pref64_addr.s6_addr32[2] & htonl(mask_a2);
+       }
+pref64_out:
+       iov[IOV_RA_PREF64].iov_base = (char *)pref64;
+       iov[IOV_RA_PREF64].iov_len = pref64_sz;
+
        /*
         * RFC7084 § 4.3 :
         *    L-3:   An IPv6 CE router MUST advertise itself as a router for the
@@ -667,7 +777,7 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr
         *           WAN interface.
         */
 
-       for (ssize_t i = 0; i < addr_cnt; ++i) {
+       for (ssize_t i = 0; i < valid_addr_cnt; ++i) {
                struct odhcpd_ipaddr *addr = &addrs[i];
                struct nd_opt_route_info *tmp;
                uint32_t valid;
@@ -746,6 +856,7 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr
        if (odhcpd_send(iface->router_event.uloop.fd, &dest, iov, ARRAY_SIZE(iov), iface) > 0)
                iface->ra_sent++;
 
+out:
        free(pfxs);
        free(routes);