diff options
-rw-r--r-- | src/config.c | 1 | ||||
-rw-r--r-- | src/netlink.c | 56 | ||||
-rw-r--r-- | src/odhcpd.h | 18 | ||||
-rw-r--r-- | src/router.c | 73 |
4 files changed, 123 insertions, 25 deletions
diff --git a/src/config.c b/src/config.c index 71b786c..31893d1 100644 --- a/src/config.c +++ b/src/config.c @@ -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); } diff --git a/src/netlink.c b/src/netlink.c index 39f6245..63a0f8b 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -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; diff --git a/src/odhcpd.h b/src/odhcpd.h index ff7e105..88c8c79 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -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; diff --git a/src/router.c b/src/router.c index 541c023..949cbe7 100644 --- a/src/router.c +++ b/src/router.c @@ -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; |