summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/config.c1
-rw-r--r--src/netlink.c56
-rw-r--r--src/odhcpd.h18
-rw-r--r--src/router.c73
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;