router: advertise removed addresses as invalid in 3 consecutive RAs
authorAlin Nastac <alin.nastac@gmail.com>
Wed, 15 Dec 2021 12:47:04 +0000 (13:47 +0100)
committerHans Dedecker <dedeckeh@gmail.com>
Mon, 10 Jan 2022 20:50:06 +0000 (21:50 +0100)
On prefix removal, router advertisement daemon is supposed to send
advertise with an invalid PI entry (see RFC 7084 L-13).

Signed-off-by: Alin Nastac <alin.nastac@gmail.com>
Signed-off-by: Hans Dedecker <dedeckeh@gmail.com>
src/config.c
src/netlink.c
src/odhcpd.h
src/router.c

index 71b786c913cc57202b1a5926ea52eb542df13c56..31893d1a2b6471490552b9b08548d2bf8e9c27cd 100644 (file)
@@ -248,6 +248,7 @@ static void close_interface(struct interface *iface)
        clean_interface(iface);
        free(iface->addr4);
        free(iface->addr6);
+       free(iface->invalid_addr6);
        free(iface->ifname);
        free(iface);
 }
index 39f6245ec82d35bbd8c9a2d371a4d8cbfe4a789b..63a0f8bd1f411ca579f8463612625bea662e03ad 100644 (file)
@@ -205,10 +205,66 @@ static void refresh_iface_addr6(int ifindex)
                        change = len != (ssize_t)iface->addr6_len;
                        for (ssize_t i = 0; !change && i < len; ++i) {
                                if (!IN6_ARE_ADDR_EQUAL(&addr[i].addr.in6, &iface->addr6[i].addr.in6) ||
+                                   addr[i].prefix != iface->addr6[i].prefix ||
                                    (addr[i].preferred > (uint32_t)now) != (iface->addr6[i].preferred > (uint32_t)now) ||
                                    addr[i].valid < iface->addr6[i].valid || addr[i].preferred < iface->addr6[i].preferred)
                                        change = true;
                        }
+
+                       if (change) {
+                               /*
+                                * Keep track on removed prefixes, so we could advertise them as invalid
+                                * for at least a couple of times.
+                                *
+                                * RFC7084 ยง 4.3 :
+                                *    L-13:  If the delegated prefix changes, i.e., the current prefix is
+                                *           replaced with a new prefix without any overlapping time
+                                *           period, then the IPv6 CE router MUST immediately advertise the
+                                *           old prefix with a Preferred Lifetime of zero and a Valid
+                                *           Lifetime of either a) zero or b) the lower of the current
+                                *           Valid Lifetime and two hours (which must be decremented in
+                                *           real time) in a Router Advertisement message as described in
+                                *           Section 5.5.3, (e) of [RFC4862].
+                                */
+
+                               for (size_t i = 0; i < iface->addr6_len; ++i) {
+                                       bool removed = true;
+
+                                       if (iface->addr6[i].valid <= (uint32_t)now)
+                                               continue;
+
+                                       for (ssize_t j = 0; removed && j < len; ++j) {
+                                               size_t plen = min(addr[j].prefix, iface->addr6[i].prefix);
+
+                                               if (odhcpd_bmemcmp(&addr[j].addr.in6, &iface->addr6[i].addr.in6, plen) == 0)
+                                                       removed = false;
+                                       }
+
+                                       for (size_t j = 0; removed && j < iface->invalid_addr6_len; ++j) {
+                                               size_t plen = min(iface->invalid_addr6[j].prefix, iface->addr6[i].prefix);
+
+                                               if (odhcpd_bmemcmp(&iface->invalid_addr6[j].addr.in6, &iface->addr6[i].addr.in6, plen) == 0)
+                                                       removed = false;
+                                       }
+
+                                       if (removed) {
+                                               size_t pos = iface->invalid_addr6_len;
+                                               struct odhcpd_ipaddr *new_invalid_addr6 = realloc(iface->invalid_addr6,
+                                                               sizeof(*iface->invalid_addr6) * (pos + 1));
+
+                                               if (!new_invalid_addr6)
+                                                       break;
+
+                                               iface->invalid_addr6 = new_invalid_addr6;
+                                               iface->invalid_addr6_len++;
+                                               memcpy(&iface->invalid_addr6[pos], &iface->addr6[i], sizeof(*iface->invalid_addr6));
+                                               iface->invalid_addr6[pos].valid = iface->invalid_addr6[pos].preferred = (uint32_t)now;
+
+                                               if (iface->invalid_addr6[pos].prefix < 64)
+                                                       iface->invalid_addr6[pos].prefix = 64;
+                                       }
+                               }
+                       }
                }
 
                iface->addr6 = addr;
index ff7e105c68c79615adf6898bf6345c853436f732..88c8c792fe6e00d506140ab38c65ffe222ab1f2b 100644 (file)
@@ -26,6 +26,9 @@
 #include <libubox/ustream.h>
 #include <libubox/vlist.h>
 
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+#define max(a, b) (((a) > (b)) ? (a) : (b))
+
 // RFC 6106 defines this router advertisement option
 #define ND_OPT_ROUTE_INFO 24
 #define ND_OPT_RECURSIVE_DNS 25
@@ -118,11 +121,16 @@ struct odhcpd_ipaddr {
        uint32_t preferred;
        uint32_t valid;
 
-       /* ipv6 only */
-       uint8_t dprefix;
+       union {
+               /* ipv6 only */
+               struct {
+                       uint8_t dprefix;
+                       uint8_t invalid_advertisements;
+               };
 
-       /* ipv4 only */
-       struct in_addr broadcast;
+               /* ipv4 only */
+               struct in_addr broadcast;
+       };
 };
 
 enum odhcpd_mode {
@@ -232,6 +240,8 @@ struct interface {
        // IPv6 runtime data
        struct odhcpd_ipaddr *addr6;
        size_t addr6_len;
+       struct odhcpd_ipaddr *invalid_addr6;
+       size_t invalid_addr6_len;
 
        // RA runtime data
        struct odhcpd_event router_event;
index 541c0237dd7d21e75e3eefc796393ece33739c77..949cbe74abe1c19cc7f522ca644189b9431b00b2 100644 (file)
@@ -441,7 +441,7 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr
        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;
+       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 +485,61 @@ 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;
 
-               addrs = alloca(size);
-               memcpy(addrs, iface->addr6, size);
+       if (valid_addr_cnt + invalid_addr_cnt) {
+               addrs = alloca(sizeof(*addrs) * (valid_addr_cnt + invalid_addr_cnt));
 
-               addr_cnt = iface->addr6_len;
+               if (valid_addr_cnt) {
+                       memcpy(addrs, iface->addr6, sizeof(*addrs) * valid_addr_cnt);
 
-               /* Check default route */
-               if (iface->default_router) {
-                       default_route = true;
+                       /* Check default route */
+                       if (iface->default_router) {
+                               default_route = true;
 
-                       if (iface->default_router > 1)
-                               valid_prefix = true;
-               } else if (parse_routes(addrs, addr_cnt))
-                       default_route = true;
+                               if (iface->default_router > 1)
+                                       valid_prefix = true;
+                       } else if (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);
+
+                       /* 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));
+
+                                       iface->invalid_addr6_len--;
+
+                                       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 +582,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,
@@ -667,7 +698,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;